CodeGym /Java-Blog /Random-DE /Besser zusammen: Java und die Thread-Klasse. Teil II – Sy...
John Squirrels
Level 41
San Francisco

Besser zusammen: Java und die Thread-Klasse. Teil II – Synchronisierung

Veröffentlicht in der Gruppe Random-DE

Einführung

Wir wissen also, dass Java Threads hat. Darüber können Sie in der Rezension mit dem Titel „ Besser zusammen: Java und die Thread-Klasse“ nachlesen. Teil I – Threads zur Ausführung . Threads sind notwendig, um parallel arbeiten zu können. Dies macht es sehr wahrscheinlich, dass die Threads irgendwie miteinander interagieren. Schauen wir uns an, wie das passiert und welche grundlegenden Werkzeuge wir haben. Besser zusammen: Java und die Thread-Klasse.  Teil II – Synchronisierung – 1

Ertrag

Thread.yield() ist verwirrend und wird selten verwendet. Es wird im Internet auf viele verschiedene Arten beschrieben. Einschließlich einiger Leute, die schreiben, dass es eine Warteschlange von Threads gibt, in die ein Thread basierend auf Thread-Prioritäten absteigt. Andere Leute schreiben, dass ein Thread seinen Status von „Running“ zu „Runnable“ ändert (obwohl es keinen Unterschied zwischen diesen Status gibt, dh Java unterscheidet nicht zwischen ihnen). Die Realität ist, dass alles viel weniger bekannt und dennoch in gewisser Weise einfacher ist. Besser zusammen: Java und die Thread-Klasse.  Teil II – Synchronisierung – 2Es ist ein Fehler ( JDK-6416721: (spec thread) Fix Thread.yield() javadocyield() ) für die Dokumentation der Methode protokolliert . Wenn man es liest, wird klar, dass dieyield()Die Methode gibt dem Java-Thread-Scheduler eigentlich nur eine Empfehlung, dass diesem Thread weniger Ausführungszeit gegeben werden kann. Aber was tatsächlich passiert, also ob der Scheduler auf die Empfehlung reagiert und was er im Allgemeinen tut, hängt von der Implementierung der JVM und dem Betriebssystem ab. Und es kann auch von einigen anderen Faktoren abhängen. Die ganze Verwirrung ist höchstwahrscheinlich auf die Tatsache zurückzuführen, dass Multithreading im Zuge der Entwicklung der Java-Sprache neu überdacht wurde. Lesen Sie hier mehr in der Übersicht: Kurze Einführung in Java Thread.yield() .

Schlafen

Ein Thread kann während seiner Ausführung in den Ruhezustand wechseln. Dies ist die einfachste Art der Interaktion mit anderen Threads. Das Betriebssystem, auf dem die virtuelle Java-Maschine ausgeführt wird, auf der unser Java-Code ausgeführt wird, verfügt über einen eigenen Thread-Scheduler . Es entscheidet, welcher Thread wann gestartet wird. Ein Programmierer kann nicht direkt über Java-Code mit diesem Scheduler interagieren, sondern nur über die JVM. Er oder sie kann den Planer bitten, den Thread für eine Weile anzuhalten, also in den Ruhezustand zu versetzen. Weitere Informationen finden Sie in diesen Artikeln: Thread.sleep() und So funktioniert Multithreading . Sie können sich auch ansehen, wie Threads in Windows-Betriebssystemen funktionieren: Interna von Windows Thread . Und jetzt sehen wir es mit eigenen Augen. Speichern Sie den folgenden Code in einer Datei mit dem Namen HelloWorldApp.java:

class HelloWorldApp {
    public static void main(String []args) {
        Runnable task = () -> {
            try {
                int secToWait = 1000 * 60;
                Thread.currentThread().sleep(secToWait);
                System.out.println("Woke 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 mit dem Befehl „ javac HelloWorldApp.java“ und führen das Programm dann mit „ java HelloWorldApp“ aus. Am besten starten Sie das Programm in einem separaten Fenster. Unter Windows sieht es beispielsweise so aus: start java HelloWorldApp. Wir verwenden den Befehl jps, um die PID (Prozess-ID) abzurufen, und öffnen die Liste der Threads mit „ jvisualvm --openpid pid: Besser zusammen: Java und die Thread-Klasse.  Teil II – Synchronisation – 3Wie Sie sehen können, hat unser Thread jetzt den Status „Schlafend“. Tatsächlich gibt es eine elegantere Möglichkeit, zu helfen Unser Thread hat süße Träume:

try {
	TimeUnit.SECONDS.sleep(60);
	System.out.println("Woke up");
} catch (InterruptedException e) {
	e.printStackTrace();
}
Ist Ihnen aufgefallen, dass wir InterruptedExceptionüberall abfertigen? Lassen Sie uns verstehen, warum.

Thread.interrupt()

Die Sache ist, dass, während ein Thread wartet/schläft, jemand ihn unterbrechen möchte. In diesem Fall kümmern wir uns um eine InterruptedException. Dieser Mechanismus wurde erstellt, nachdem die Thread.stop()Methode als veraltet, also veraltet und unerwünscht, erklärt wurde. Der Grund dafür war, dass stop()der Thread beim Aufruf der Methode einfach „getötet“ wurde, was sehr unvorhersehbar war. Wir konnten nicht wissen, wann der Thread gestoppt werden würde, und wir konnten die Datenkonsistenz nicht garantieren. Stellen Sie sich vor, Sie schreiben Daten in eine Datei, während der Thread beendet wird. Anstatt den Thread zu beenden, entschieden die Entwickler von Java, dass es logischer wäre, ihm mitzuteilen, dass er unterbrochen werden sollte. Wie auf diese Informationen reagiert wird, muss der Thread selbst entscheiden. Weitere Informationen finden Sie unter Warum ist Thread.stop veraltet?auf der Website von Oracle. 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. Stattdessen zeigen wir sofort „Unterbrochen“ an. Dies liegt daran, dass wir die interrupt()Methode im Thread aufgerufen haben. Diese Methode setzt ein 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 läuft möglicherweise und wartet nicht auf etwas, sondern führt lediglich Aktionen aus. Es kann jedoch damit gerechnet werden, dass andere die Arbeit zu einem bestimmten Zeitpunkt beenden 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 whilewird die Schleife so lange ausgeführt, bis der Thread von außen unterbrochen wird. Was das Flag betrifft , ist es wichtig zu wissen, dass das isInterrupted-Flag zurückgesetzt wird, isInterruptedwenn wir ein abfangen , und dann „false“ zurückgibt. Die Thread-Klasse verfügt auch über eine statische Thread.interrupted()- Methode, die nur für den aktuellen Thread gilt, aber diese Methode setzt das Flag auf false zurück! Lesen Sie mehr in diesem Kapitel mit dem Titel Thread-Unterbrechung . InterruptedExceptionisInterrupted()

Beitreten (Warten Sie, bis ein weiterer Thread beendet ist)

Die einfachste Art des Wartens besteht darin, darauf zu warten, dass ein anderer Thread beendet 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 ruhen. Gleichzeitig wartet der Hauptthread, bis der schlafende Thread aufwacht und seine Arbeit beendet. Wenn Sie sich den Status des Threads in JVisualVM ansehen, dann sieht er so aus: Besser zusammen: Java und die Thread-Klasse.  Teil II – Synchronisierung – 4Dank Überwachungstools können Sie sehen, was mit dem Thread los ist. Die joinMethode ist ziemlich einfach, da es sich lediglich um eine Methode mit Java-Code handelt, die wait()so lange ausgeführt wird, wie der Thread, in dem sie aufgerufen wird, aktiv ist. Sobald der Thread stirbt (wenn er mit seiner Arbeit fertig ist), wird das Warten unterbrochen. Und das ist die ganze Magie der join()Methode. Kommen wir also zum Interessantesten.

Monitor

Multithreading umfasst das Konzept eines Monitors. Das Wort „Monitor“ stammt aus dem Lateinischen des 16. Jahrhunderts und bedeutet „ein Instrument oder Gerät, das zur Beobachtung, Überprüfung oder kontinuierlichen Aufzeichnung eines Prozesses verwendet wird“. Im Rahmen dieses Artikels werden wir versuchen, die Grundlagen zu behandeln. Für alle, die Einzelheiten erfahren möchten, schauen Sie sich bitte die verlinkten Materialien an. Wir beginnen unsere Reise mit der Java Language Specification (JLS): 17.1. Synchronisierung . Darin heißt es: Besser zusammen: Java und die Thread-Klasse.  Teil II – Synchronisierung – 5Es stellt sich heraus, dass Java einen „Monitor“-Mechanismus für die Synchronisation zwischen Threads verwendet. Jedem Objekt ist ein Monitor zugeordnet, und Threads können es mit abrufen lock()oder mit freigeben unlock(). Als nächstes finden wir das Tutorial auf der Oracle-Website: Intrinsic Locks and Synchronization. In diesem Tutorial heißt es, dass die Synchronisierung von Java auf einer internen Entität basiert, die als intrinsische Sperre oder Monitorsperre bezeichnet wird . Dieses Schloss wird oft einfach als „ Monitor “ bezeichnet. Wir sehen auch wieder, dass jedem Objekt in Java eine intrinsische Sperre zugeordnet ist. Sie können Java – Intrinsic Locks and Synchronization lesen . Als nächstes ist es wichtig zu verstehen, wie ein Objekt in Java einem Monitor zugeordnet werden kann. In Java verfügt jedes Objekt über einen Header, der interne Metadaten speichert, 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 Objekt-Header enthält ein „Markierungswort“, das wie folgt aussieht: Besser zusammen: Java und die Thread-Klasse.  Teil II – Synchronisierung – 6

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

Hier ist ein sehr nützlicher JavaWorld-Artikel: Wie die Java Virtual Machine die Thread-Synchronisierung durchführt . Dieser Artikel sollte mit der Beschreibung aus dem Abschnitt „Zusammenfassung“ des folgenden Problems im JDK-Fehlerverfolgungssystem kombiniert werden: JDK-8183909 . Das Gleiche können Sie hier lesen: JEP-8183909 . In Java ist also ein Monitor einem Objekt zugeordnet und wird verwendet, um einen Thread zu blockieren, wenn der Thread versucht, die Sperre zu erlangen (oder zu erhalten). Hier ist das einfachste Beispiel:

public class HelloWorld{
    public static void main(String []args){
        Object object = new Object();
        synchronized(object) {
            System.out.println("Hello World");
        }
    }
}
Hier verwendet der aktuelle Thread (derjenige, in dem diese Codezeilen ausgeführt werden) das synchronizedSchlüsselwort, um zu versuchen, den mit dem verknüpften Monitor zu verwendenobject"\Variable, um die Sperre zu erhalten/zu erwerben. Wenn niemand sonst um den Monitor kämpft (dh niemand sonst synchronisierten Code mit demselben Objekt ausführt), versucht Java möglicherweise, eine Optimierung namens „Biased Locking“ durchzuführen. Dem Markierungswort im Objektheader werden ein relevanter Tag und ein Datensatz darüber hinzugefügt, welcher Thread die Sperre des Monitors besitzt. Dies reduziert den Aufwand, der zum Sperren eines Monitors erforderlich ist. Wenn der Monitor zuvor einem anderen Thread gehörte, reicht eine solche Sperre nicht aus. Die JVM wechselt zur nächsten Art der Sperrung: „Basissperrung“. Es verwendet Vergleichs- und Austauschoperationen (CAS). Darüber hinaus speichert das Markierungswort des Objektheaders selbst nicht mehr das Markierungswort, sondern einen Verweis darauf, wo es gespeichert ist, und das Tag ändert sich, sodass die JVM versteht, dass wir grundlegendes Sperren verwenden. Wenn mehrere Threads um einen Monitor konkurrieren (kämpfen) (einer hat die Sperre erhalten und ein zweiter wartet darauf, dass die Sperre aufgehoben wird), ändert sich das Tag im Markierungswort und das Markierungswort speichert nun einen Verweis auf den Monitor als Objekt – eine interne Entität der JVM. Wie im JDK Enchancement Proposal (JEP) angegeben, erfordert diese Situation Platz im nativen Heap-Speicherbereich, um diese Entität zu speichern. Der Verweis auf den Speicherort dieser internen Entität wird im Markierungswort des Objektheaders gespeichert. Somit ist ein Monitor eigentlich ein Mechanismus zum Synchronisieren des Zugriffs auf gemeinsam genutzte Ressourcen zwischen mehreren Threads. Die JVM wechselt zwischen mehreren Implementierungen dieses Mechanismus. Wenn wir also über den Monitor sprechen, sprechen wir der Einfachheit halber eigentlich von Schlössern. und eine Sekunde wartet darauf, dass die Sperre aufgehoben wird), dann ändert sich das Tag im Markierungswort und das Markierungswort speichert nun einen Verweis auf den Monitor als Objekt – eine interne Entität der JVM. Wie im JDK Enchancement Proposal (JEP) angegeben, erfordert diese Situation Platz im nativen Heap-Speicherbereich, um diese Entität zu speichern. Der Verweis auf den Speicherort dieser internen Entität wird im Markierungswort des Objektheaders gespeichert. Somit ist ein Monitor eigentlich ein Mechanismus zum Synchronisieren des Zugriffs auf gemeinsam genutzte Ressourcen zwischen mehreren Threads. Die JVM wechselt zwischen mehreren Implementierungen dieses Mechanismus. Wenn wir also über den Monitor sprechen, sprechen wir der Einfachheit halber eigentlich von Schlössern. und eine Sekunde wartet darauf, dass die Sperre aufgehoben wird), dann ändert sich das Tag im Markierungswort und das Markierungswort speichert nun einen Verweis auf den Monitor als Objekt – eine interne Entität der JVM. Wie im JDK Enchancement Proposal (JEP) angegeben, erfordert diese Situation Platz im nativen Heap-Speicherbereich, um diese Entität zu speichern. Der Verweis auf den Speicherort dieser internen Entität wird im Markierungswort des Objektheaders gespeichert. Somit ist ein Monitor eigentlich ein Mechanismus zum Synchronisieren des Zugriffs auf gemeinsam genutzte Ressourcen zwischen mehreren Threads. Die JVM wechselt zwischen mehreren Implementierungen dieses Mechanismus. Wenn wir also über den Monitor sprechen, sprechen wir der Einfachheit halber eigentlich von Schlössern. und das Markierungswort speichern nun einen Verweis auf den Monitor als Objekt – eine interne Entität der JVM. Wie im JDK Enchancement Proposal (JEP) angegeben, erfordert diese Situation Platz im nativen Heap-Speicherbereich, um diese Entität zu speichern. Der Verweis auf den Speicherort dieser internen Entität wird im Markierungswort des Objektheaders gespeichert. Somit ist ein Monitor eigentlich ein Mechanismus zum Synchronisieren des Zugriffs auf gemeinsam genutzte Ressourcen zwischen mehreren Threads. Die JVM wechselt zwischen mehreren Implementierungen dieses Mechanismus. Wenn wir also über den Monitor sprechen, sprechen wir der Einfachheit halber eigentlich von Schlössern. und das Markierungswort speichern nun einen Verweis auf den Monitor als Objekt – eine interne Entität der JVM. Wie im JDK Enchancement Proposal (JEP) angegeben, erfordert diese Situation Platz im nativen Heap-Speicherbereich, um diese Entität zu speichern. Der Verweis auf den Speicherort dieser internen Entität wird im Markierungswort des Objektheaders gespeichert. Somit ist ein Monitor eigentlich ein Mechanismus zum Synchronisieren des Zugriffs auf gemeinsam genutzte Ressourcen zwischen mehreren Threads. Die JVM wechselt zwischen mehreren Implementierungen dieses Mechanismus. Wenn wir also über den Monitor sprechen, sprechen wir der Einfachheit halber eigentlich von Schlössern. Der Verweis auf den Speicherort dieser internen Entität wird im Markierungswort des Objektheaders gespeichert. Somit ist ein Monitor eigentlich ein Mechanismus zum Synchronisieren des Zugriffs auf gemeinsam genutzte Ressourcen zwischen mehreren Threads. Die JVM wechselt zwischen mehreren Implementierungen dieses Mechanismus. Wenn wir also über den Monitor sprechen, sprechen wir der Einfachheit halber eigentlich von Schlössern. Der Verweis auf den Speicherort dieser internen Entität wird im Markierungswort des Objektheaders gespeichert. Somit ist ein Monitor eigentlich ein Mechanismus zum Synchronisieren des Zugriffs auf gemeinsam genutzte Ressourcen zwischen mehreren Threads. Die JVM wechselt zwischen mehreren Implementierungen dieses Mechanismus. Wenn wir also über den Monitor sprechen, sprechen wir der Einfachheit halber eigentlich von Schlössern. Besser zusammen: Java und die Thread-Klasse.  Teil II – Synchronisierung – 7

Synchronisiert (wartet auf eine Sperre)

Wie wir bereits gesehen haben, ist das Konzept eines „synchronisierten Blocks“ (oder „kritischen Abschnitts“) eng mit dem Konzept eines Monitors verbunden. Schauen Sie sich 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 übergibt der Hauptthread zunächst das Aufgabenobjekt an den neuen Thread, erhält dann sofort die Sperre und führt damit eine lange Operation aus (8 Sekunden). Während dieser Zeit kann die Aufgabe nicht fortfahren, da sie den synchronizedBlock nicht betreten kann, da die Sperre bereits erworben wurde. Wenn der Thread die Sperre nicht erhalten kann, wartet er auf den Monitor. Sobald die Sperre erreicht ist, wird die Ausführung fortgesetzt. Wenn ein Thread einen Monitor verlässt, gibt er die Sperre auf. In JVisualVM sieht es so aus: Besser zusammen: Java und die Thread-Klasse.  Teil II – Synchronisierung – 8Wie Sie in JVisualVM sehen können, ist der Status „Monitor“, was bedeutet, dass der Thread blockiert ist und den Monitor nicht übernehmen kann. Sie können auch Code verwenden, um den Status eines Threads zu bestimmen, aber die auf diese Weise ermittelten Statusnamen stimmen nicht mit den in JVisualVM verwendeten Namen überein, obwohl sie ähnlich sind. In diesem Fall ist dieth1.getState()Die Anweisung in der for-Schleife gibt BLOCKED zurück , da der Monitor des Objekts, solange die Schleife ausgeführt wird, lockvom Thread belegt ist mainund der th1Thread blockiert ist und nicht fortfahren kann, bis die Sperre aufgehoben wird. Zusätzlich zu synchronisierten Blöcken kann eine gesamte Methode synchronisiert werden. Hier ist zum Beispiel eine Methode aus der HashTableKlasse:

public synchronized int size() {
	return count;
}
Diese Methode wird jeweils nur von einem Thread ausgeführt. Brauchen wir das Schloss wirklich? Ja, wir brauchen es. Bei Instanzmethoden fungiert das „this“-Objekt (aktuelles Objekt) als Sperre. Zu diesem Thema gibt es hier eine interessante Diskussion: Gibt es einen Vorteil bei der Verwendung einer synchronisierten Methode anstelle eines synchronisierten Blocks? . Wenn die Methode statisch ist, ist die Sperre nicht das „this“-Objekt (da es für eine statische Methode kein „this“-Objekt gibt), sondern ein Klassenobjekt (z. B. ) Integer.class.

Warten (Warten auf einen Monitor). Methoden notify() und notifyAll()

Die Thread-Klasse verfügt über eine weitere Wartemethode, die einem Monitor zugeordnet ist. Im Gegensatz zu sleep()and join()kann diese Methode nicht einfach aufgerufen werden. Sein Name ist wait(). Die waitMethode wird für das Objekt aufgerufen, das dem Monitor zugeordnet ist, auf den wir warten möchten. Sehen wir uns ein Beispiel an:

public static void main(String []args) throws InterruptedException {
	    Object lock = new Object();
	    // The task object will wait until it is notified via lock
	    Runnable task = () -> {
	        synchronized(lock) {
	            try {
	                lock.wait();
	            } catch(InterruptedException e) {
	                System.out.println("interrupted");
	            }
	        }
	        // After we are notified, we will wait until we can acquire the lock
	        System.out.println("thread");
	    };
	    Thread taskThread = new Thread(task);
	    taskThread.start();
        // We sleep. Then we acquire the lock, notify, and release the lock
	    Thread.currentThread().sleep(3000);
	    System.out.println("main");
	    synchronized(lock) {
	        lock.notify();
	    }
}
In JVisualVM sieht es so aus: Besser zusammen: Java und die Thread-Klasse.  Teil II – Synchronisation – 10Um zu verstehen, wie das funktioniert, denken Sie daran, dass die Methoden wait()und notify()mit verknüpft sind java.lang.Object. Es mag seltsam erscheinen, dass Thread-bezogene Methoden in der ObjectKlasse enthalten sind. Aber der Grund dafür kommt jetzt ans Licht. Sie werden sich erinnern, dass jedes Objekt in Java einen Header hat. Der Header enthält verschiedene Verwaltungsinformationen, darunter auch Informationen über den Monitor, also den Status der Sperre. Denken Sie daran, dass jedes Objekt oder jede Instanz einer Klasse einer internen Entität in der JVM zugeordnet ist, die als intrinsische Sperre oder Monitor bezeichnet wird. Im obigen Beispiel gibt der Code für das Aufgabenobjekt an, dass wir den synchronisierten Block für den mit dem lockObjekt verknüpften Monitor eingeben. Wenn es uns gelingt, die Sperre für diesen Monitor zu erlangen, dannwait()wird genannt. Der Thread, der die Aufgabe ausführt, gibt den lockMonitor des Objekts frei, tritt aber in die Warteschlange der Threads ein, die auf eine Benachrichtigung vom lockMonitor des Objekts warten. Diese Thread-Warteschlange wird WAIT SET genannt, was ihren Zweck besser widerspiegelt. Das heißt, es handelt sich eher um eine Menge als um eine Warteschlange. Der mainThread erstellt einen neuen Thread mit dem Aufgabenobjekt, startet ihn und wartet 3 Sekunden. Dies macht es sehr wahrscheinlich, dass der neue Thread die Sperre vor dem Thread erhalten mainund in die Warteschlange des Monitors gelangen kann. Danach tritt der mainThread selbst in den locksynchronisierten Block des Objekts ein und führt mithilfe des Monitors eine Thread-Benachrichtigung durch. Nachdem die Benachrichtigung gesendet wurde, maingibt der Thread die freilockDer neue Thread, der zuvor auf die lockFreigabe des Objektmonitors gewartet hat, setzt die Ausführung fort. notify()Es ist möglich, eine Benachrichtigung nur an einen Thread ( ) oder gleichzeitig an alle Threads in der Warteschlange ( ) zu senden notifyAll(). Lesen Sie hier mehr: Unterschied zwischen notify() und notifyAll() in Java . Es ist wichtig zu beachten, dass die Benachrichtigungsreihenfolge davon abhängt, wie die JVM implementiert wird. Lesen Sie hier mehr: Wie kann man Hunger mit notify und notifyAll lösen? . Die Synchronisierung kann ohne Angabe eines Objekts durchgeführt werden. Sie können dies tun, wenn eine gesamte Methode und nicht nur ein einzelner Codeblock synchronisiert wird. Bei statischen Methoden ist die Sperre beispielsweise ein Klassenobjekt (abgerufen über .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 eine Methode nicht statisch ist, wird die Synchronisierung mit der aktuellen Methode durchgeführt instance, d. h. mit this. Übrigens haben wir bereits erwähnt, dass Sie die getState()Methode verwenden können, um den Status eines Threads abzurufen. Beispielsweise lautet der Status eines Threads in der Warteschlange, der auf einen Monitor wartet, WAITING oder TIMED_WAITING, wenn die wait()Methode eine Zeitüberschreitung angegeben hat. Besser zusammen: Java und die Thread-Klasse.  Teil II – Synchronisation – 11

https://stackoverflow.com/questions/36425942/what-is-the-lifecycle-of-thread-in-java

Thread-Lebenszyklus

Im Laufe seines Lebens ändert sich der Status eines Threads. Tatsächlich umfassen diese Änderungen den Lebenszyklus des Threads. Sobald ein Thread erstellt wird, ist sein Status NEU. In diesem Zustand läuft der neue Thread noch nicht und der Java-Thread-Scheduler weiß noch nichts davon. Damit der Thread-Scheduler etwas über den Thread erfahren kann, müssen Sie die thread.start()Methode aufrufen. Dann wechselt der Thread in den RUNNABLE-Status. Im Internet gibt es viele falsche Diagramme, die zwischen den Zuständen „Ausführbar“ und „Ausgeführt“ unterscheiden. Das ist aber ein Fehler, denn Java unterscheidet nicht zwischen „ready to work“ (runnable) und „working“ (runnable). Wenn ein Thread aktiv, aber nicht aktiv (nicht ausführbar) ist, befindet er sich in einem von zwei Zuständen:
  • BLOCKIERT – Warten auf den Eintritt in einen kritischen Abschnitt, z. B. einen synchronizedBlock.
  • WAITING – Warten darauf, dass ein anderer Thread eine Bedingung erfüllt.
Wenn die Bedingung erfüllt ist, startet der Thread-Scheduler den Thread. Wenn der Thread bis zu einer bestimmten Zeit wartet, ist sein Status TIMED_WAITING. Wenn der Thread nicht mehr läuft (er ist beendet oder es wurde eine Ausnahme ausgelöst), wechselt er in den Status TERMINATED. Um den Status eines Threads herauszufinden, verwenden Sie die getState()Methode. Threads verfügen außerdem über eine isAlive()Methode, die „true“ zurückgibt, wenn der Thread nicht TERMINATED ist.

LockSupport und Thread-Parken

Ab Java 1.6 erschien ein interessanter Mechanismus namens LockSupport . Besser zusammen: Java und die Thread-Klasse.  Teil II – Synchronisation – 12Diese Klasse ordnet jedem Thread, der sie verwendet, eine „Erlaubnis“ zu. Ein Aufruf der park()Methode kehrt sofort zurück, wenn die Genehmigung verfügbar ist, wobei die Genehmigung dabei verbraucht wird. Andernfalls blockiert es. Durch den Aufruf der unparkMethode wird die Genehmigung verfügbar gemacht, falls sie noch nicht verfügbar ist. Es gibt nur 1 Genehmigung. Die Java-Dokumentation für LockSupportbezieht sich auf die SemaphoreKlasse. 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) {
            // Request the permit and wait until we get it
            e.printStackTrace();
        }
        System.out.println("Hello, World!");
    }
}
Dieser Code wird immer warten, da das Semaphor jetzt 0 Berechtigungen hat. Und wenn acquire()im Code aufgerufen wird (also die Erlaubnis anfordern), wartet der Thread, bis er die Erlaubnis erhält. Da wir warten, müssen wir uns darum kümmern InterruptedException. Interessanterweise erhält das Semaphor einen separaten Thread-Status. Wenn wir in JVisualVM nachsehen, werden wir sehen, dass der Status nicht „Warten“, sondern „Parken“ ist. Besser zusammen: Java und die Thread-Klasse.  Teil II – Synchronisation – 13Schauen wir uns ein anderes Beispiel an:

public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            // Park the current thread
            System.err.println("Will be Parked");
            LockSupport.park();
            // As soon as we are unparked, we will start to act
            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 Status des Threads ist WAITING, aber JVisualVM unterscheidet zwischen waitdem synchronizedSchlüsselwort und parkder LockSupportKlasse. Warum ist das LockSupportso wichtig? Wir wenden uns noch einmal der Java-Dokumentation zu und schauen uns den Thread-Status WAITING an . Wie Sie sehen, gibt es nur drei Möglichkeiten, hineinzukommen. Zwei dieser Möglichkeiten sind wait()und join(). Und der dritte ist LockSupport. In Java können Sperren auch auf t aufgebaut werden LockSupporund bieten Tools auf höherer Ebene. Versuchen wir, eines zu verwenden. Schauen Sie sich 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();
    }
}
Genau wie in den vorherigen Beispielen ist auch hier alles einfach. Das lockObjekt wartet darauf, dass jemand die gemeinsam genutzte Ressource freigibt. Wenn wir in JVisualVM nachsehen, werden wir sehen, dass der neue Thread geparkt wird, bis der mainThread die Sperre für ihn aufhebt. Weitere Informationen zu Sperren finden Sie hier: Java 8 StampedLocks vs. ReadWriteLocks und Synchronized and Lock API in Java. Um besser zu verstehen, wie Sperren implementiert werden, ist es hilfreich, in diesem Artikel über Phaser zu lesen: Leitfaden für den Java Phaser . Und was die verschiedenen Synchronisierer betrifft, lesen Sie unbedingt den DZone- Artikel über die Java-Synchronisierer.

Abschluss

In dieser Rezension haben wir die wichtigsten Arten der Thread-Interaktion in Java untersucht. Zusätzliches Material: Besser zusammen: Java und die Thread-Klasse. Teil I – Ausführungsthreads Gemeinsam besser: Java und die Thread-Klasse. Teil III – Gemeinsam besser interagieren: Java und die Thread-Klasse. Teil IV – Callable, Future und Freunde Gemeinsam besser: Java und die Thread-Klasse. Teil V – Executor, ThreadPool, Fork/Join Gemeinsam besser: Java und die Thread-Klasse. Teil VI – Feuer los!
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION