it-swarm.com.de

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 ein IllegalMonitorStateException ausgelöst. Aber was ist der Grund für diese Einschränkung? Ich weiß, dass wait() den Monitor freigibt, aber warum müssen wir die explizit erwerben Überwachen Sie, indem Sie einen bestimmten Block synchronisieren, und geben Sie den Monitor dann frei, indem Sie wait() aufrufen.

Was ist der potenzielle Schaden, wenn es möglich wäre, wait() außerhalb eines synchronisierten Blocks aufzurufen und dabei dessen Semantik beizubehalten - den Aufruferthread anzuhalten?

247
diy

Eine wait() ist nur dann sinnvoll, wenn es auch eine notify() gibt. Es geht also immer um die Kommunikation zwischen Threads, und diese muss synchronisiert werden, damit sie ordnungsgemäß funktioniert. Man könnte argumentieren, dass dies implizit sein sollte, aber das würde aus folgendem Grund nicht wirklich helfen:

Semantisch gesehen ist man nie nur wait(). Sie benötigen eine Bedingung, um zufrieden zu sein, und wenn dies nicht der Fall ist, warten Sie, bis dies der Fall ist. Also, was Sie wirklich tun, ist

if(!condition){
    wait();
}

Die Bedingung wird jedoch von einem separaten Thread festgelegt. Damit dies ordnungsgemäß funktioniert, ist eine Synchronisierung erforderlich.

Noch ein paar Dinge stimmen damit nicht:

  • Sie können falsche Weckrufe erhalten (was bedeutet, dass ein Thread vom 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 wieder aufruft).

Um mit diesen Fällen fertig zu werden, brauchen Sie immer eine Variation davon:

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

Besser noch, verwirren Sie sich überhaupt nicht mit den Synchronisationsprimitiven und arbeiten Sie mit den Abstraktionen, die in Java.util.concurrent Pakete.

223

Was ist der potenzielle Schaden, wenn es möglich wäre, wait() außerhalb eines synchronisierten Blocks aufzurufen und dabei seine Semantik beizubehalten - den Aufruferthread anzuhalten?

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

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

Ein erster Versuch (ohne Synchronisation) könnte in etwa so aussehen

class BlockingQueue {
    Queue<String> buffer = new LinkedList<String>();

    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() auf und sieht, dass buffer.isEmpty().

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

  3. Der Consumer-Thread ruft nun wait() (und miss das gerade aufgerufene notify() auf.

  4. Wenn Sie Pech haben, wird der Producer-Thread nicht mehr give() produzieren, da der Consumer-Thread nie aufwacht und wir einen Deadlock haben.

Sobald Sie das Problem verstanden haben, liegt die Lösung auf der Hand: Verwenden Sie synchronized, um sicherzustellen, dass notify niemals zwischen isEmpty und wait aufgerufen wird.

Ohne auf Details einzugehen: Dieses Synchronisierungsproblem ist universell. Wie Michael Borgwardt ausführt, dreht sich beim Warten/Benachrichtigen alles um die Kommunikation zwischen Threads, sodass Sie am Ende immer eine Racebedingung haben, die der oben beschriebenen ähnelt. Aus diesem Grund wird die Regel "Nur in synchronisiertem Zustand warten" durchgesetzt.


Ein Absatz aus dem Link gepostet von @Willie fasst es ganz gut zusammen:

Sie benötigen eine absolute Garantie, dass der Kellner und der Melder sich über den Zustand des Prädikats einig sind. Der Kellner prüft den Status des Prädikats etwas, bevor es in den Schlaf geht, aber es hängt von der Richtigkeit des Prädikats ab, wenn es in den Schlaf geht. Zwischen diesen beiden Ereignissen besteht eine Schwachstelle, die das Programm unterbrechen kann.

Das Prädikat, auf das sich Produzent und Konsument einigen müssen, ist im obigen Beispiel buffer.isEmpty(). Die Vereinbarung wird gelöst, indem sichergestellt wird, dass das Warten und Benachrichtigen in synchronized Blöcken ausgeführt wird.


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

272
aioobe

@ Rollerball ist richtig. Die wait() wird aufgerufen, so dass der Thread warten kann, bis eine Bedingung eintritt, wenn dieser wait() -Aufruf erfolgt, und der Thread gezwungen ist, seine Sperre aufzugeben.
Um etwas aufzugeben, musst du es zuerst besitzen. Thread muss zuerst die Sperre besitzen. Daher muss es in einer synchronized Methode/einem _ Block aufgerufen werden.

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

Hier ist eine nette Lektüre ..

11

Wenn Sie nicht vor wait() synchronisieren, kann das folgende Problem auftreten:

  1. Wenn der erste Thread in makeChangeOnX() geht und die while-Bedingung überprüft und true ist (x.metCondition() gibt false zurück, bedeutet x.condition ist false), also wird es hineinkommen. Kurz vor der Methode wait() wechselt ein anderer Thread zu setConditionToTrue() und setzt x.condition Auf true und notifyAll().
  2. Erst danach gibt der erste Thread seine wait() -Methode ein (nicht betroffen von der notifyAll(), die wenige Momente zuvor stattgefunden hat). In diesem Fall wartet der erste Thread darauf, dass ein anderer Thread setConditionToTrue() ausführt. Dies kann jedoch vorkommen, dass der erste Thread nicht erneut ausgeführt wird.

Wenn Sie jedoch synchronized vor die Methoden stellen, die den Objektstatus ändern, geschieht dies nicht.

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;
    }
}
4
Shay.G

Wir alle wissen, dass die Methoden wait (), notify () und notifyAll () für die Kommunikation zwischen Threads verwendet werden. Um fehlendes Signal und fehlerhafte Weckprobleme zu beseitigen, wartet der wartende Thread unter bestimmten Bedingungen. z.B.-

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

Dann benachrichtigen Garnituren wasNotified Variable auf true und benachrichtigen.

Jeder Thread hat seinen lokalen Cache, sodass alle Änderungen zuerst dort geschrieben und dann nach und nach in den Hauptspeicher übertragen werden.

Wären diese Methoden nicht innerhalb des synchronisierten Blocks aufgerufen worden, würde die Variable wasNotified nicht in den Hauptspeicher geschrieben und sich dort im lokalen Cache des Threads befinden, sodass der wartende Thread weiterhin auf das Signal wartet, obwohl es durch Benachrichtigung des Threads zurückgesetzt wurde.

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

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

Danke, hoffe es klärt sich.

2
Aavesh Yadav

direkt aus this Java Oracle Tutorial:

Wenn ein Thread d.wait aufruft, muss er die intrinsische Sperre für d besitzen - andernfalls wird ein Fehler ausgelöst. Das Aufrufen von wait in einer synchronisierten Methode ist eine einfache Methode, um die interne Sperre abzurufen.

0
Rollerball

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

Java untersucht nur den synchronisierten Codeblock, der vom Objekt t gesperrt wurde. Java kann nicht den gesamten Code durchsuchen, um eine bestimmte t.wait () zu benachrichtigen.

0
lakshman reddy

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

Wenn Sie synchronized nicht zusammen mit wait() oder notify() verwenden, kann ein anderer Thread could denselben Block eingeben, anstatt zu warten der Monitor, um es zu betreten. Wenn darüber hinaus z.B. Wenn Sie auf ein Array ohne einen synchronisierten Block zugreifen, wird die Änderung in einem anderen Thread möglicherweise nicht angezeigt. Tatsächlich wird in einem anderen Thread wird nicht angezeigt Änderungen in diesem Thread angezeigt wann hat bereits eine Kopie des Arrays im X-Level-Cache (auch bekannt als 1st/2nd/3rd-Level-Cache) des CPU-Kerns für die Thread-Verarbeitung.

Synchronisierte Blöcke sind jedoch nur eine Seite der Medaille: Wenn Sie tatsächlich aus einem nicht synchronisierten Kontext auf ein Objekt in einem synchronisierten Kontext zugreifen, wird das Objekt auch innerhalb eines synchronisierten Blocks 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 ?

Darüber hinaus bin ich überzeugt, dass die Caches auf x-Ebene für die meisten nicht reproduzierbaren Laufzeitfehler verantwortlich sind. Dies liegt daran, dass die Entwickler in der Regel nicht mit den grundlegenden Themen wie der Funktionsweise der CPU oder den Auswirkungen der Speicherhierarchie auf die Ausführung von Anwendungen vertraut sind: http://en.wikipedia.org/wiki/Memory_hierarchy

Es bleibt ein Rätsel, warum Programmierklassen nicht zuerst mit der Speicherhierarchie und der CPU-Architektur beginnen. "Hallo Welt" hilft hier nicht weiter. ;)

0
Marcus

gemäß docs:

Der aktuelle Thread muss den Monitor dieses Objekts besitzen. Der Thread gibt den Besitz dieses Monitors frei.

wait() Methode bedeutet einfach, dass die Sperre für das Objekt aufgehoben wird. Das Objekt wird also nur innerhalb des synchronisierten Blocks/der synchronisierten Methode gesperrt. Wenn sich der Thread außerhalb des Synchronisationsblocks befindet, bedeutet dies, dass er nicht gesperrt ist. Wenn er nicht gesperrt ist, was würden Sie dann für das Objekt freigeben?

0
Arun Raaj