JavaRush /Java-Blog /Random-DE /Sie können Java nicht mit einem Thread ruinieren: Teil II...
Viacheslav
Level 3

Sie können Java nicht mit einem Thread ruinieren: Teil II – Synchronisierung

Veröffentlicht in der Gruppe Random-DE

Einführung

Wir wissen also, dass es Threads in Java gibt, worüber Sie in der Rezension „ You Can’t Spoil Java with a Thread: Part I – Threads “ nachlesen können . Threads werden benötigt, um gleichzeitig arbeiten zu können. Daher ist es sehr wahrscheinlich, dass die Threads irgendwie miteinander interagieren. Lassen Sie uns verstehen, wie dies geschieht und welche grundlegenden Kontrollen wir haben. Sie können Java nicht mit einem Thread ruinieren: Teil II – Synchronisierung – 1

Ertrag

Die Thread.yield()- Methode ist mysteriös und wird selten verwendet. Es gibt viele Variationen seiner Beschreibung im Internet. Bis zu dem Punkt, dass einige über eine Art Thread-Warteschlange schreiben, in der der Thread unter Berücksichtigung seiner Prioritäten nach unten verschoben wird. Jemand schreibt, dass der Thread seinen Status von „Ausgeführt“ in „Ausführbar“ ändern wird (obwohl es keine Unterteilung in diese Status gibt und Java nicht zwischen ihnen unterscheidet). Aber in Wirklichkeit ist alles viel unbekannter und gewissermaßen einfacher. Sie können Java nicht mit einem Thread ruinieren: Teil II – Synchronisierung – 2Zum Thema Methodendokumentation yieldgibt es einen Fehler „ JDK-6416721: (spec thread) Fix Thread.yield() javadoc “. Wenn Sie es lesen, wird klar, dass die Methode tatsächlich yieldnur eine Empfehlung an den Java-Thread-Scheduler übermittelt, dass diesem Thread weniger Ausführungszeit gegeben werden kann. Aber was tatsächlich passiert, ob der Scheduler die Empfehlung hört und was er im Allgemeinen tut, hängt von der Implementierung der JVM und dem Betriebssystem ab. Oder vielleicht aufgrund anderer Faktoren. Die ganze Verwirrung war höchstwahrscheinlich auf das Umdenken in Bezug auf Multithreading während der Entwicklung der Java-Sprache zurückzuführen. Weitere Informationen finden Sie in der Rezension „ Kurze Einführung in Java Thread.yield() “.

Schlaf - Einschlaffaden

Ein Thread kann während seiner Ausführung in den Ruhezustand fallen. Dies ist die einfachste Art der Interaktion mit anderen Threads. Das Betriebssystem, auf dem die Java Virtual Machine installiert ist, auf dem Java-Code ausgeführt wird, verfügt über einen eigenen Thread-Scheduler, den sogenannten Thread Scheduler. Er entscheidet, welcher Thread wann ausgeführt wird. Der Programmierer kann nicht direkt über den Java-Code mit diesem Scheduler interagieren, aber er kann über die JVM den Scheduler bitten, den Thread für eine Weile anzuhalten, um ihn „in den Ruhezustand zu versetzen“. Mehr erfahren Sie in den Artikeln „ Thread.sleep() “ und „ So funktioniert Multithreading “. Darüber hinaus erfahren Sie, wie Threads im Windows-Betriebssystem funktionieren: „ Internals of Windows Thread “. Jetzt werden wir es mit eigenen Augen sehen. Speichern wir den folgenden Code in einer Datei HelloWorldApp.java:
class HelloWorldApp {
    public static void main(String []args) {
        Runnable task = () -> {
            try {
                int secToWait = 1000 * 60;
                Thread.currentThread().sleep(secToWait);
                System.out.println("Waked up");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(task);
        thread.start();
    }
}
Wie Sie sehen, haben wir eine Aufgabe, die 60 Sekunden wartet, danach endet das Programm. Wir kompilieren javac HelloWorldApp.javaund führen es aus java HelloWorldApp. Es ist besser, in einem separaten Fenster zu starten. Unter Windows würde es beispielsweise so aussehen: start java HelloWorldApp. Mit dem Befehl jps ermitteln wir die PID des Prozesses und öffnen die Liste der Threads mit jvisualvm --openpid pidПроцесса: Sie können Java nicht mit einem Thread ruinieren: Teil II – Synchronisierung – 3Wie Sie sehen können, ist unser Thread in den Ruhezustand übergegangen. Tatsächlich lässt sich das Schlafen des aktuellen Threads noch schöner gestalten:
try {
	TimeUnit.SECONDS.sleep(60);
	System.out.println("Waked up");
} catch (InterruptedException e) {
	e.printStackTrace();
}
Sie haben wahrscheinlich bemerkt, dass wir überall verarbeiten InterruptedException? Lassen Sie uns verstehen, warum.

Unterbrechen eines Threads oder Thread.interrupt

Die Sache ist die: Während der Thread im Ruhezustand wartet, möchte vielleicht jemand dieses Warten unterbrechen. In diesem Fall behandeln wir eine solche Ausnahme. Dies geschah, nachdem die Methode Thread.stopals veraltet erklärt wurde, d. h. veraltet und für die Verwendung unerwünscht. Der Grund dafür war, dass beim Aufruf der Methode stopder Thread einfach „getötet“ wurde, was sehr unvorhersehbar war. Wir konnten nicht wissen, wann der Fluss gestoppt werden würde, und wir konnten die Konsistenz der Daten nicht garantieren. Stellen Sie sich vor, Sie schreiben Daten in eine Datei und dann wird der Stream zerstört. Daher entschieden sie, dass es logischer wäre, den Fluss nicht zu unterbrechen, sondern ihm mitzuteilen, dass er unterbrochen werden sollte. Wie darauf reagiert wird, bleibt dem Fluss selbst überlassen. Weitere Einzelheiten finden Sie in Oracles „ Warum ist Thread.stop veraltet? “ Schauen wir uns ein Beispiel an:
public static void main(String []args) {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(60);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
In diesem Beispiel warten wir nicht 60 Sekunden, sondern geben sofort „Unterbrochen“ aus. Dies liegt daran, dass wir die Methode des Threads aufgerufen haben interrupt. Diese Methode setzt „internes Flag namens Interrupt-Status“. Das heißt, jeder Thread verfügt über ein internes Flag, auf das nicht direkt zugegriffen werden kann. Aber wir haben native Methoden für die Interaktion mit dieser Flagge. Aber das ist nicht der einzige Weg. Ein Thread kann sich im Ausführungsprozess befinden und nicht auf etwas warten, sondern lediglich Aktionen ausführen. Es kann jedoch vorgesehen sein, dass sie es an einem bestimmten Punkt ihrer Arbeit abschließen möchten. Zum Beispiel:
public static void main(String []args) {
	Runnable task = () -> {
		while(!Thread.currentThread().isInterrupted()) {
			//Do some work
		}
		System.out.println("Finished");
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
Im obigen Beispiel können Sie sehen, dass die Schleife so lange whileläuft, bis der Thread von außen unterbrochen wird. Das Wichtige, was Sie über das isInterrupted-InterruptedException Flag wissen sollten, ist, dass das Flag zurückgesetzt wird, wenn wir es abfangen isInterrupted, und dann „ isInterruptedfalse“ zurückgibt. Es gibt auch eine statische Methode in der Thread-Klasse, die nur für den aktuellen Thread gilt – Thread.interrupted() , aber diese Methode setzt das Flag auf false zurück! Mehr erfahren Sie im Kapitel „ Thread-Unterbrechung “.

Beitreten – Warten auf den Abschluss eines weiteren Threads

Die einfachste Art des Wartens besteht darin, darauf zu warten, dass ein anderer Thread abgeschlossen wird.
public static void main(String []args) throws InterruptedException {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(5);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.join();
	System.out.println("Finished");
}
In diesem Beispiel wird der neue Thread 5 Sekunden lang in den Ruhezustand versetzt. Gleichzeitig wartet der Hauptthread, bis der schlafende Thread aufwacht und seine Arbeit beendet. Wenn Sie JVisualVM durchsehen, sieht der Status des Threads folgendermaßen aus: Sie können Java nicht mit einem Thread ruinieren: Teil II – Synchronisierung – 4Dank Überwachungstools können Sie sehen, was mit dem Thread passiert. Die Methode joinist recht einfach, da es sich lediglich um eine Methode mit Java-Code handelt, die ausgeführt wird, waitwährend der Thread, in dem sie aufgerufen wird, aktiv ist. Sobald der Thread stirbt (bei Beendigung), wird das Warten beendet. Das ist die ganze Magie der Methode join. Kommen wir daher zum interessantesten Teil.

Konzeptmonitor

Im Multithreading gibt es so etwas wie Monitor. Im Allgemeinen wird das Wort Monitor aus dem Lateinischen mit „Aufseher“ oder „Aufseher“ übersetzt. Im Rahmen dieses Artikels werden wir versuchen, uns an das Wesentliche zu erinnern, und für diejenigen, die möchten, bitte ich Sie, für Details in das Material über die Links einzutauchen. Beginnen wir unsere Reise mit der Java-Sprachspezifikation, also mit JLS: „ 17.1. Synchronisation “. Darin heißt es: Sie können Java nicht mit einem Thread ruinieren: Teil II – Synchronisierung – 5Es stellt sich heraus, dass Java zum Zweck der Synchronisierung zwischen Threads einen bestimmten Mechanismus namens „Monitor“ verwendet. Jedem Objekt ist ein Monitor zugeordnet, und Threads können es sperren oder entsperren. Als nächstes finden wir auf der Oracle-Website ein Schulungs-Tutorial: „ Intrinsic Locks and Synchronization “. In diesem Tutorial wird erklärt, dass die Synchronisierung in Java auf einer internen Entität basiert, die als intrinsische Sperre oder Monitorsperre bezeichnet wird. Oft wird ein solches Schloss einfach als „Monitor“ bezeichnet. Wir sehen auch wieder, dass jedem Objekt in Java eine intrinsische Sperre zugeordnet ist. Sie können „ Java – Intrinsische Sperren und Synchronisation “ lesen . Als nächstes ist es wichtig zu verstehen, wie ein Objekt in Java einem Monitor zugeordnet werden kann. Jedes Objekt in Java verfügt über einen Header – eine Art interne Metadaten, die dem Programmierer nicht über den Code zur Verfügung stehen, die die virtuelle Maschine jedoch benötigt, um korrekt mit Objekten zu arbeiten. Der Objektheader enthält ein MarkWord, das wie folgt aussieht: Sie können Java nicht mit einem Thread ruinieren: Teil II – Synchronisierung – 6

https://edu.netbeans.org/contrib/slides/java-overview-and-java-se6.pdf

Ein Artikel von Habr ist hier sehr nützlich: „ Aber wie funktioniert Multithreading? Teil I: Synchronisation .“ Zu diesem Artikel lohnt es sich, eine Beschreibung aus der Zusammenfassung des Aufgabenblocks vom JDK-Bugtaker hinzuzufügen: „ JDK-8183909 “. Das Gleiche können Sie auch in „ JEP-8183909 “ lesen. In Java ist also ein Monitor einem Objekt zugeordnet und der Thread kann diesen Thread blockieren, oder man sagt auch „eine Sperre erhalten“. Das einfachste Beispiel:
public class HelloWorld{
    public static void main(String []args){
        Object object = new Object();
        synchronized(object) {
            System.out.println("Hello World");
        }
    }
}
Mit dem Schlüsselwort synchronizedversucht der aktuelle Thread (in dem diese Codezeilen ausgeführt werden) also, den mit dem Objekt verknüpften Monitor zu verwenden objectund „eine Sperre zu erhalten“ oder „den Monitor zu erfassen“ (die zweite Option ist sogar vorzuziehen). Wenn es keine Konkurrenz für den Monitor gibt (d. h. niemand anderes möchte mit demselben Objekt synchronisieren), kann Java versuchen, eine Optimierung namens „Biased Locking“ durchzuführen. Der Titel des Objekts in Mark Word enthält das entsprechende Tag und eine Aufzeichnung darüber, an welchen Thread der Monitor angehängt ist. Dies reduziert den Overhead bei der Aufnahme des Monitors. Wenn der Monitor bereits zuvor an einen anderen Thread gebunden war, reicht diese Sperre nicht aus. Die JVM wechselt zum nächsten Sperrtyp – der Grundsperre. Es verwendet Vergleichs- und Austauschoperationen (CAS). Gleichzeitig speichert der Header in Mark Word nicht mehr Mark Word selbst, sondern ein Link zu seinem Speicher + das Tag wird geändert, sodass die JVM erkennt, dass wir grundlegende Sperren verwenden. Wenn mehrere Threads um den Monitor konkurrieren (einer hat den Monitor erfasst und der zweite wartet auf die Freigabe des Monitors), ändert sich das Tag in Mark Word und Mark Word beginnt, einen Verweis auf den Monitor als zu speichern ein Objekt – eine interne Entität der JVM. Wie im JEP angegeben, ist in diesem Fall Speicherplatz im nativen Heap-Speicherbereich erforderlich, um diese Entität zu speichern. Der Link zum Speicherort dieser internen Entität befindet sich im Mark Word-Objekt. Wie wir sehen, ist der Monitor also tatsächlich ein Mechanismus, der die Synchronisierung des Zugriffs mehrerer Threads auf gemeinsam genutzte Ressourcen gewährleistet. Es gibt mehrere Implementierungen dieses Mechanismus, zwischen denen die JVM wechselt. Wenn wir also von einem Monitor sprechen, sprechen wir der Einfachheit halber eigentlich von Schlössern. Sie können Java nicht mit einem Thread ruinieren: Teil II – Synchronisierung – 7

Synchronisiert und wartend per Sperre

Das Konzept eines Monitors ist, wie wir bereits gesehen haben, eng mit dem Konzept eines „Synchronisationsblocks“ (oder, wie es auch genannt wird, eines kritischen Abschnitts) verbunden. Schauen wir uns ein Beispiel an:
public static void main(String[] args) throws InterruptedException {
	Object lock = new Object();

	Runnable task = () -> {
		synchronized (lock) {
			System.out.println("thread");
		}
	};

	Thread th1 = new Thread(task);
	th1.start();
	synchronized (lock) {
		for (int i = 0; i < 8; i++) {
			Thread.currentThread().sleep(1000);
			System.out.print("  " + i);
		}
		System.out.println(" ...");
	}
}
Hier sendet der Hauptthread die Aufgabe zunächst an einen neuen Thread, „erfasst“ dann sofort die Sperre und führt damit eine lange Operation aus (8 Sekunden). Während dieser ganzen Zeit kann die Aufgabe den Block für ihre Ausführung nicht betreten synchronized, weil die Schleuse ist bereits belegt. Wenn ein Thread keine Sperre erhalten kann, wartet er am Monitor darauf. Sobald es es erhält, wird es mit der Ausführung fortfahren. Wenn ein Thread den Monitor verlässt, gibt er die Sperre auf. In JVisualVM würde es so aussehen: Sie können Java nicht mit einem Thread ruinieren: Teil II – Synchronisierung – 8Wie Sie sehen, heißt der Status in JVisualVM „Monitor“, da der Thread blockiert ist und den Monitor nicht belegen kann. Sie können den Status des Threads auch im Code herausfinden, aber der Name dieses Status stimmt nicht mit den JVisualVM-Begriffen überein, obwohl sie ähnlich sind. In diesem Fall gibt th1.getState()die Schleife BLOCKED zurück , weil Während die Schleife ausgeführt wird, ist der Monitor vom Thread belegt und der Thread ist blockiert und kann nicht weiterarbeiten, bis die Sperre zurückgegeben wird. Zusätzlich zu Synchronisationsblöcken kann eine gesamte Methode synchronisiert werden. Zum Beispiel eine Methode aus der Klasse : forlockmainth1HashTable
public synchronized int size() {
	return count;
}
In einer Zeiteinheit wird diese Methode nur von einem Thread ausgeführt. Aber wir brauchen ein Schloss, oder? Ja ich brauche es. Bei Objektmethoden ist die Sperre this. Zu diesem Thema gibt es eine interessante Diskussion: „ Gibt es einen Vorteil, eine synchronisierte Methode anstelle eines synchronisierten Blocks zu verwenden? “. Wenn die Methode statisch ist, erfolgt die Sperre nicht this(da es für eine statische Methode keine geben kann this), sondern das Klassenobjekt (z. B. Integer.class).

Warten und Warten auf dem Monitor. Die Methoden notify und notifyAll

Thread verfügt über eine weitere Wartemethode, die mit dem Monitor verbunden ist. Im Gegensatz zu sleepand joinkann es nicht einfach aufgerufen werden. Und sein Name ist wait. Die Methode wird waitauf dem Objekt ausgeführt, auf dessen Monitor wir warten möchten. Sehen wir uns ein Beispiel an:
public static void main(String []args) throws InterruptedException {
	    Object lock = new Object();
	    // task будет ждать, пока его не оповестят через lock
	    Runnable task = () -> {
	        synchronized(lock) {
	            try {
	                lock.wait();
	            } catch(InterruptedException e) {
	                System.out.println("interrupted");
	            }
	        }
	        // После оповещения нас мы будем ждать, пока сможем взять лок
	        System.out.println("thread");
	    };
	    Thread taskThread = new Thread(task);
	    taskThread.start();
        // Ждём и после этого забираем себе лок, оповещаем и отдаём лок
	    Thread.currentThread().sleep(3000);
	    System.out.println("main");
	    synchronized(lock) {
	        lock.notify();
	    }
}
In JVisualVM sieht es so aus: Sie können Java nicht mit einem Thread ruinieren: Teil II – Synchronisierung – 10Um zu verstehen, wie das funktioniert, sollten Sie bedenken, dass Methoden waitauf . Es scheint seltsam, dass Thread-bezogene Methoden in der . Aber hierin liegt die Antwort. Wie wir uns erinnern, hat jedes Objekt in Java einen Header. Der Header enthält verschiedene Serviceinformationen, darunter auch Informationen zum Monitor – Daten zum Sperrzustand. Und wie wir uns erinnern, hat jedes Objekt (d. h. jede Instanz) eine Verbindung zu einer internen JVM-Entität, die als intrinsische Sperre bezeichnet wird und auch als Monitor bezeichnet wird. Im obigen Beispiel beschreibt die Aufgabe, dass wir den Synchronisierungsblock auf dem mit verknüpften Monitor eingeben . Wenn es möglich ist, eine Sperre für diesen Monitor zu erhalten, dann . Der Thread, der diese Aufgabe ausführt, gibt den Monitor frei , wird jedoch in die Warteschlange der Threads aufgenommen, die auf eine Benachrichtigung auf dem Monitor warten . Diese Thread-Warteschlange heißt WAIT-SET, was das Wesentliche besser widerspiegelt. Es ist eher ein Set als eine Warteschlange. Der Thread erstellt einen neuen Thread mit der Task-Task, startet ihn und wartet 3 Sekunden. Dies ermöglicht mit hoher Wahrscheinlichkeit, dass ein neuer Thread die Sperre vor dem Thread übernimmt und sich auf dem Monitor in die Warteschlange stellt. Danach tritt der Thread selbst in den Synchronisationsblock ein und führt eine Benachrichtigung des Threads auf dem Monitor durch. Nachdem die Benachrichtigung gesendet wurde, gibt der Thread den Monitor frei und der neue Thread (der zuvor gewartet hat) setzt die Ausführung fort, nachdem er auf die Freigabe des Monitors gewartet hat. Es ist möglich, eine Benachrichtigung nur an einen der Threads ( ) oder an alle Threads in der Warteschlange gleichzeitig zu senden ( ). Weitere Informationen finden Sie unter „ Unterschied zwischen notify() und notifyAll() in Java “. Es ist wichtig zu beachten, dass die Benachrichtigungsreihenfolge von der JVM-Implementierung abhängt. Weitere Informationen finden Sie unter „ Wie löst man Hunger mit notify und notifyall? “. Die Synchronisierung kann ohne Angabe eines Objekts durchgeführt werden. Dies ist möglich, wenn nicht ein einzelner Codeabschnitt synchronisiert wird, sondern eine ganze Methode. Bei statischen Methoden ist die Sperre beispielsweise das Klassenobjekt (abgerufen über ): notifyjava.lang.ObjectObjectlockwaitlocklockmainmainmainlockmainlocklocknotifynotifyAll.class
public static synchronized void printA() {
	System.out.println("A");
}
public static void printB() {
	synchronized(HelloWorld.class) {
		System.out.println("B");
	}
}
Hinsichtlich der Verwendung von Sperren sind beide Methoden gleich. Wenn die Methode nicht statisch ist, wird die Synchronisierung gemäß dem aktuellen durchgeführt instance, d. h. gemäß this. Übrigens haben wir bereits erwähnt, dass getStateSie mit dieser Methode den Status eines Threads ermitteln können. Hier ist also ein Thread, der vom Monitor in die Warteschlange gestellt wird. Der Status lautet WAITING oder TIMED_WAITING, wenn die Methode waitein Wartezeitlimit angegeben hat. Sie können Java nicht mit einem Thread ruinieren: Teil II – Synchronisierung – 11

Lebenszyklus eines Threads

Wie wir gesehen haben, verändert der Fluss im Laufe des Lebens seinen Zustand. Im Wesentlichen sind diese Änderungen der Lebenszyklus des Threads. Wenn ein Thread gerade erstellt wird, hat er den Status NEU. In dieser Position ist er noch nicht gestartet und der Java Thread Scheduler weiß noch nichts über den neuen Thread. Damit der Thread-Scheduler über einen Thread Bescheid weiß, müssen Sie die aufrufen thread.start(). Dann wechselt der Thread in den RUNNABLE-Zustand. Es gibt viele falsche Schemata im Internet, bei denen die Zustände „Runnable“ und „Running“ getrennt sind. Aber das ist ein Fehler, denn... Java unterscheidet nicht zwischen den Status „ready to run“ und „running“. Wenn ein Thread aktiv, aber nicht aktiv (nicht ausführbar) ist, befindet er sich in einem von zwei Zuständen:
  • BLOCKIERT – wartet auf den Eintritt in einen geschützten Bereich, d. h. zum synchonizedBlock.
  • WAITING – wartet auf einen anderen Thread basierend auf einer Bedingung. Wenn die Bedingung wahr ist, startet der Thread-Scheduler den Thread.
Wenn ein Thread zeitlich wartet, befindet er sich im Status TIMED_WAITING. Wenn der Thread nicht mehr läuft (erfolgreich abgeschlossen oder mit einer Ausnahme), geht er in den Status TERMINATED über. Um den Zustand eines Threads (seinen Zustand) herauszufinden, wird die Methode verwendet getState. Threads verfügen außerdem über eine Methode isAlive, die „true“ zurückgibt, wenn der Thread nicht beendet ist.

LockSupport und Thread-Parken

Seit Java 1.6 gab es einen interessanten Mechanismus namens LockSupport . Sie können Java nicht mit einem Thread ruinieren: Teil II – Synchronisierung – 12Diese Klasse ordnet jedem Thread, der sie verwendet, eine „Erlaubnis“ oder Berechtigung zu. Der Methodenaufruf parkkehrt sofort zurück, wenn eine Genehmigung verfügbar ist, und belegt während des Aufrufs dieselbe Genehmigung. Ansonsten ist es gesperrt. Durch den Aufruf der Methode unparkwird die Genehmigung verfügbar gemacht, sofern sie noch nicht verfügbar ist. Es gibt nur 1 Genehmigung. In der Java-API ist LockSupporteine bestimmte Semaphore. Schauen wir uns ein einfaches Beispiel an:
import java.util.concurrent.Semaphore;
public class HelloWorldApp{

    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(0);
        try {
            semaphore.acquire();
        } catch (InterruptedException e) {
            // Просим разрешение и ждём, пока не получим его
            e.printStackTrace();
        }
        System.out.println("Hello, World!");
    }
}
Dieser Code wird ewig warten, da das Semaphor jetzt die Erlaubnis 0 hat. Und wenn der Thread im Code aufgerufen wird acquire(d. h. um Erlaubnis bittet), wartet er, bis er die Erlaubnis erhält. Da wir warten, sind wir zur Bearbeitung verpflichtet InterruptedException. Interessanterweise implementiert ein Semaphor einen separaten Thread-Status. Wenn wir in JVisualVM nachsehen, werden wir sehen, dass unser Status nicht Warten, sondern Parken ist. Mit einem Thread kann man Java nicht ruinieren: Teil II – Synchronisierung – 13Schauen wir uns ein weiteres Beispiel an:
public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            //Запаркуем текущий поток
            System.err.println("Will be Parked");
            LockSupport.park();
            // Как только нас распаркуют - начнём действовать
            System.err.println("Unparked");
        };
        Thread th = new Thread(task);
        th.start();
        Thread.currentThread().sleep(2000);
        System.err.println("Thread state: " + th.getState());

        LockSupport.unpark(th);
        Thread.currentThread().sleep(2000);
}
Der Thread-Status lautet WAITING, aber JVisualVM unterscheidet waitzwischen from synchronizedund parkfrom LockSupport. Warum ist dieser so wichtig LockSupport? Wenden wir uns noch einmal der Java-API zu und schauen uns Thread State WAITING an . Wie Sie sehen, gibt es nur drei Möglichkeiten, hineinzukommen. 2 Möglichkeiten – dies waitund join. Und der dritte ist LockSupport. Sperren in Java basieren auf denselben Prinzipien LockSupportund stellen Werkzeuge höherer Ebene dar. Versuchen wir, eines zu verwenden. Schauen wir uns zum Beispiel an ReentrantLock:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class HelloWorld{

    public static void main(String []args) throws InterruptedException {
        Lock lock = new ReentrantLock();
        Runnable task = () -> {
            lock.lock();
            System.out.println("Thread");
            lock.unlock();
        };
        lock.lock();

        Thread th = new Thread(task);
        th.start();
        System.out.println("main");
        Thread.currentThread().sleep(2000);
        lock.unlock();
    }
}
Wie in den vorherigen Beispielen ist auch hier alles einfach. lockwartet darauf, dass jemand eine Ressource freigibt. Wenn wir in JVisualVM nachsehen, werden wir sehen, dass der neue Thread geparkt wird, bis mainder Thread ihm die Sperre erteilt. Weitere Informationen zu Sperren finden Sie hier: „ Multithread-Programmierung in Java 8. Teil zwei. Zugriff auf veränderbare Objekte synchronisieren “ und „ Java Lock API. Theorie und Anwendungsbeispiel “. Um die Implementierung von Sperren besser zu verstehen, ist es hilfreich, etwas über Phazer in der Übersicht „ Phaser-Klasse “ zu lesen. Und wenn Sie über verschiedene Synchronizer sprechen, müssen Sie den Artikel auf Habré „ Java.util.concurrent.* Synchronizers Reference “ lesen.

Gesamt

In dieser Rezension haben wir uns mit den wichtigsten Interaktionsmöglichkeiten von Threads in Java befasst. Zusätzliches Material: #Wjatscheslaw
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION