Warum muss wait () immer im synchronisierten Block sein

Wir alle wissen, dass zum Aufrufen von Object.wait() dieser Aufruf in einen synchronisierten Block gestellt werden muss, andernfalls wird eine IllegalMonitorStateException ausgetriggers. Aber was ist der Grund für diese Einschränkung? Ich weiß, dass wait() den Monitor freigibt, aber warum müssen wir den Monitor explizit akquirieren, indem wir einen bestimmten Block synchronisieren und dann den Monitor freigeben, indem wir wait() aufrufen?

Was ist der potentielle Schaden, wenn es möglich war, wait() außerhalb eines synchronisierten Blocks aufzurufen und dabei seine Semantik beizubehalten – den Anruferthread zu unterbrechen?

Eine wait() macht nur Sinn, wenn es auch eine notify() , also geht es immer um die Kommunikation zwischen Threads, und die Synchronisierung muss korrekt funktionieren. Man könnte argumentieren, dass dies implizit sein sollte, aber das würde aus folgendem Grund nicht wirklich helfen:

Semantisch wait() Sie nie nur wait() . Du brauchst eine Bedingung, um gesättigt zu werden, und wenn es nicht ist, wartest du, bis es ist. Also was du wirklich tust ist

 if(!condition){ wait(); } 

Die Bedingung wird jedoch durch einen separaten Thread festgelegt. Um dies richtig arbeiten zu können, benötigen Sie eine Synchronisierung.

Ein paar weitere Dinge sind nicht in Ordnung, denn nur weil Ihr Thread aufgehört hat zu warten, bedeutet das nicht, dass die Bedingung, nach der Sie suchen, wahr ist:

  • Sie können falsche Wakeups bekommen (was bedeutet, dass ein Thread aus dem Warten aufwachen kann, ohne jemals eine Benachrichtigung erhalten zu haben), oder

  • Die Bedingung kann gesetzt werden, aber ein dritter Thread macht die Bedingung wieder falsch, wenn der wartende Thread aufwacht (und den Monitor erneut anfordert).

Um mit diesen Fällen umzugehen, brauchen Sie immer etwas Abwechslung:

 synchronized(lock){ while(!condition){ lock.wait(); } } 

Besser noch, machen Sie sich nicht mit den Synchronisations-Primitiven herum und arbeiten Sie mit den Abstraktionen, die in den java.util.concurrent angeboten werden.

Was ist der potentielle Schaden, wenn es möglich war, wait() außerhalb eines synchronisierten Blocks aufzurufen und dabei seine Semantik beizubehalten – den Anruferthread zu unterbrechen?

Lassen Sie uns veranschaulichen, auf welche Probleme wir stoßen würden, wenn wait() außerhalb eines synchronisierten Blocks mit einem konkreten Beispiel aufgerufen werden könnte.

Angenommen, wir würden eine blockierende Warteschlange implementieren (ich weiß, dass es bereits eine in der API gibt 🙂

Ein erster Versuch (ohne Synchronisation) könnte etwas in den folgenden Zeilen aussehen

 class BlockingQueue { Queue buffer = new LinkedList(); public void give(String data) { buffer.add(data); notify(); // Since someone may be waiting in take! } public String take() throws InterruptedException { while (buffer.isEmpty()) // don't use "if" due to spurious wakeups. wait(); return buffer.remove(); } } 

Dies könnte möglicherweise passieren:

  1. Ein Consumer-Thread ruft take() und sieht, dass buffer.isEmpty() .

  2. Bevor der Consumer-Thread wait() aufruft, kommt ein Producer-Thread und ruft ein vollständiges give() , d. buffer.add(data); notify(); buffer.add(data); notify();

  3. Der Consumer-Thread ruft nun wait() (und vermisst die notify() , die gerade aufgerufen wurde).

  4. Wenn es unglücklich ist, produziert der Produzenten-Thread nicht mehr give() , weil der Consumer-Thread niemals aufwacht und wir einen Dead-Lock haben.

Sobald Sie das Problem verstanden haben, ist die Lösung offensichtlich: isEmpty immer ” give / notify und ” isEmpty / wait atomar aus.

Ohne ins Detail zu gehen: Dieses Synchronisationsproblem ist universell. Wie Michael Borgwardt darauf hinweist, dreht sich bei wait / notify alles um die Kommunikation zwischen den Threads, so dass Sie immer mit einer ähnlichen Wettlaufsituation wie oben beschrieben enden. Dies ist der Grund, warum die Regel “nur im synchronisierten Zustand warten” erzwungen wird.


Ein Absatz aus dem von @Willie geposteten Link fasst das ziemlich gut zusammen:

Sie benötigen eine absolute Garantie, dass der Kellner und der Anmelder über den Zustand des Prädikats einig sind. Der Kellner prüft den Zustand des Prädikats zu einem gewissen Zeitpunkt, bevor es schlafen geht, aber es hängt von der Richtigkeit ab, ob das Prädikat WAHR ist, wenn es schläft. Zwischen diesen beiden Ereignissen besteht eine Zeit der Anfälligkeit, die das Programm unterbrechen kann.

Das Prädikat, auf das sich Produzent und Konsument verständigen müssen, ist im obigen Beispiel buffer.isEmpty() . Und die Vereinbarung wird getriggers, indem sichergestellt wird, dass das Warten und Benachrichtigen in synchronized Blöcken durchgeführt wird.


Dieser Beitrag wurde hier als Artikel umgeschrieben: Java: Warum wait muss in einem synchronisierten Block aufgerufen werden

@ Rollerball hat Recht. Der wait() wird aufgerufen, damit der Thread auf eine Bedingung warten kann, wenn dieser wait() Aufruf erfolgt, und der Thread gezwungen ist, seine Sperre aufzugeben.
Um etwas aufzugeben, musst du es zuerst besitzen. Der Thread muss zuerst die Sperre besitzen. Daher muss es in einer synchronized Methode / Block aufgerufen werden.

Ja, ich stimme allen obigen Antworten in Bezug auf mögliche Schäden / Inkonsistenzen zu, wenn Sie die Bedingung innerhalb der synchronized Methode / Block nicht überprüft haben. Wie jedoch @ shrini1000 darauf hingewiesen hat, wird das Aufrufen von wait() innerhalb des synchronisierten Blocks nicht verhindern, dass diese Inkonsistenz auftritt.

Hier ist eine nette Lektüre ..

Das Problem, das es verursachen kann, wenn Sie nicht vor wait() synchronisieren, ist wie folgt:

  1. Wenn der 1. Thread in makeChangeOnX() und die while-Bedingung überprüft, und es true ( x.metCondition() gibt false , bedeutet x.condition ist false ), so wird es in das x.condition hineingelangen. Dann, unmittelbar vor der Methode wait() , geht ein anderer Thread zu setConditionToTrue() und setzt die x.condition auf true und notifyAll() .
  2. Erst danach kommt der 1. Thread in seine wait() -Methode (nicht betroffen von notifyAll() , das wenige Augenblicke zuvor notifyAll() ). In diesem Fall bleibt der erste Thread auf einen anderen Thread warten, um setConditionToTrue() auszuführen, aber das passiert möglicherweise nicht noch einmal.

Aber wenn Sie vor den Methoden, die den Objektzustand ändern, eine synchronized , wird dies nicht passieren.

 class A { private Object X; makeChangeOnX(){ while (! x.getCondition()){ wait(); } // Do the change } setConditionToTrue(){ x.condition = true; notifyAll(); } setConditionToFalse(){ x.condition = false; notifyAll(); } bool getCondition(){ return x.condition; } } 

Wir alle wissen, dass die Methoden wait (), notify () und notifyAll () für die Kommunikation zwischen Threads verwendet werden. Um verpasste Signale und unerwünschte Aufweckprobleme zu beseitigen, wartet der wartende Thread immer auf einige Bedingungen. z.B-

 boolean wasNotified = false; while(!wasNotified) { wait(); } 

Dann Benachrichtigen Thread-Sets warnotierte Variable auf wahr und benachrichtigen.

Jeder Thread hat seinen lokalen Cache, so dass alle Änderungen zuerst dorthin geschrieben und dann schrittweise in den Hauptspeicher übertragen werden.

Wenn diese Methoden nicht innerhalb des synchronisierten Blocks aufgerufen würden, würde die Variable wasNotified nicht in den Hauptspeicher geleert und wäre dort im lokalen Cache des Threads, so dass der wartende Thread weiterhin auf das Signal wartet, obwohl er durch Benachrichtigen des Threads zurückgesetzt wurde.

Um diese Art von Problemen zu beheben, werden diese Methoden immer innerhalb eines synchronisierten Blocks aufgerufen, der sicherstellt, dass beim Start des synchronisierten Blocks alles aus dem Hauptspeicher gelesen und in den Hauptspeicher geleert wird, bevor der synchronisierte Block verlassen wird.

 synchronized(monitor) { boolean wasNotified = false; while(!wasNotified) { wait(); } } 

Danke, hoffe es klärt.

direkt von diesem Java-oracle-Tutorial:

Wenn ein Thread d.wait aufruft, muss er die intrinsische Sperre für d besitzen – andernfalls wird ein Fehler ausgetriggers. Der Aufruf von wait innerhalb einer synchronisierten Methode ist eine einfache Möglichkeit, die intrinsische Sperre zu erhalten.

Dies hat im Wesentlichen mit der Hardware-Architektur zu tun (dh RAM und Caches ).

Wenn Sie nicht synchronized mit wait() oder notify() , könnte ein anderer Thread denselben Block eingeben, anstatt darauf zu warten, dass der Monitor ihn eingibt. Wenn z. B. auf ein Array ohne einen synchronisierten Block zugegriffen wird, kann ein anderer Thread die Änderung nicht sehen … tatsächlich wird ein anderer Thread keine Änderungen daran sehen, wenn er bereits eine Kopie des Arrays in dem Cache der x-Ebene hat ( alias 1. / 2. / 3.-Level-Caches) des Thread-Handling-CPU-cores.

Aber synchronisierte Blöcke sind nur eine Seite der Medaille: Wenn Sie tatsächlich von einem nicht synchronisierten Kontext aus auf ein Objekt innerhalb eines synchronisierten Kontexts zugreifen, wird das Objekt auch innerhalb eines synchronisierten Blocks noch nicht synchronisiert, da es eine eigene Kopie des Objekts enthält Objekt in seinem Cache. Ich habe hier über dieses Problem geschrieben: https://StackOverflow.com/a/21462631 und Wenn eine Sperre ein nicht endgültiges Objekt enthält, kann die Referenz des Objekts noch von einem anderen Thread geändert werden?

Außerdem bin ich davon überzeugt, dass die x-level Caches für die meisten nicht reproduzierbaren Laufzeiterrors verantwortlich sind. Das liegt daran, dass die Entwickler die Low-Level-Sachen normalerweise nicht lernen, wie CPU-Arbeit oder wie sich die Speicherhierarchie auf das Ausführen von Anwendungen auswirkt: http://en.wikipedia.org/wiki/Memory_hierarchy

Es bleibt ein Rätsel, warum Programmierklassen nicht zuerst mit Speicherhierarchie und CPU-Architektur beginnen. “Hallo Welt” wird hier nicht helfen. 😉

Wenn Sie notify () von einem Objekt t aufrufen, benachrichtigt java eine bestimmte t.wait () -Methode. Aber, wie sucht und benennt Java eine bestimmte Wartemethode.

Java schaut nur in den synchronisierten Codeblock, der vom Objekt t gesperrt wurde. Java kann nicht den gesamten Code durchsuchen, um einen bestimmten t.wait () zu benachrichtigen.