2009-06-24 7 views
42

ich gelesen habe, dass wir immer eine wait() innerhalb einer Schleife aufgerufen sollte:Warum sollte() warten immer in einer Schleife aufgerufen werden

while (!condition) { obj.wait(); } 

Es funktioniert gut, ohne eine Schleife so, warum ist das so?

+2

Hinweis: Oracle-Dokumentation nennt dieses Idiom einen "geschützten Block" https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html –

Antwort

7

Möglicherweise gibt es mehr als nur einen Arbeiter, der auf eine Bedingung wartet, um wahr zu werden.

Wenn zwei oder mehr Arbeiter wach werden (notifyAll), müssen sie den Zustand erneut prüfen. ansonsten würden alle Arbeiter weiterarbeiten, obwohl möglicherweise nur Daten für einen von ihnen vorhanden sind.

5

Da werden warten und notify verwendet [Variablen Bedingung] zu implementieren (http://en.wikipedia.org/wiki/Monitor_(synchronization)#Blocking_condition_variables) und so müssen Sie, ob das spezifische Prädikat zu überprüfen, auf Sie warten, bevor Sie fortfahren wahr ist.

+0

kd304 ist richtig - es ist nicht nur, dass eine Bedingung möglicherweise nicht wurden erfüllt - es ist die Tatsache, dass ein Thread fälschlicherweise von einer Wartezeit aufwachen kann –

+0

@oxbow_lakes Wie weit der Thread wartet auf eine Bedingung betroffen ist, gibt es keinen wirklichen Unterschied zwischen einem unechten Wake-up und einem Notify, das signalisieren soll eine andere Bedingung. In jedem Fall müssen Sie Ihr Prädikat überprüfen. –

56

Sie müssen sich nicht nur auf Schleife es aber Überprüfen Sie Ihre Bedingung in der Schleife. Java garantiert nicht, dass Ihr Thread nur durch einen Notify()/NotifyAll() - Aufruf oder den richtigen Notify()/NotifyAll() -Aufruf überhaupt geweckt wird. Aufgrund dieser Eigenschaft ist die Schleife Eine geringere Version funktioniert möglicherweise in Ihrer Entwicklungsumgebung und schlägt in der Produktionsumgebung unerwartet fehl.

Zum Beispiel warten Sie auf etwas:

synchronized (theObjectYouAreWaitingOn) { 
    while (!carryOn) { 
     theObjectYouAreWaitingOn.wait(); 
    } 
} 

Ein böser Faden kommt und:

theObjectYouAreWaitingOn.notifyAll(); 

Wenn der böse Faden nicht/kann nicht verwirren mit dem carryOn Sie einfach weiter für den richtigen Kunden warten.

Bearbeiten: Hinzugefügt einige weitere Beispiele. Die Wartezeit kann unterbrochen werden. Es löst InterruptedException aus, und Sie müssen die Wartezeit möglicherweise in einen try-catch umbrechen. Abhängig von Ihren geschäftlichen Anforderungen können Sie die Ausnahme beenden oder unterdrücken und weiter warten.

+4

Dies ist die richtige Antwort. Die Dokumentation für warten: http://java.sun.com/javase/6/docs/api/java/lang/Object.html#wait(long) ... beschreibt eigentlich, warum Sie es in eine setzen müssen Loop - falsche Wakeups. Hat das OP es gelesen? –

+0

Ah, 'falsche Wakeups'. Ich konnte mich nicht an den Namen erinnern. – akarnokd

+1

Sie sollten Ihr Codebeispiel aktualisieren, um die ThreadInterruptedException für Ihre Wartezeit zu verarbeiten. Es würde dann mit Ihrer Antwort übereinstimmen. – Robin

31

Es ist in documentation for Object.wait(long milis)

beantwortet aufwachen

Ein Thread kann eine sogenannte unechte Wakeup auch mitgeteilt, unterbrochen oder Zeitüberschreitung, ohne. Während dies in der Praxis selten vorkommt, müssen Anwendungen dagegen vorgehen, indem sie nach der Bedingung suchen, die den Thread geweckt haben sollte, und weiter warten, wenn die Bedingung nicht erfüllt ist. Mit anderen Worten, sollte wartet treten immer in Schleifen, wie diese:

synchronized (obj) { 
    while (<condition does not hold>) 
     obj.wait(timeout); 
    ... // Perform action appropriate to condition 
} 

(Weitere Informationen zu diesem Thema finden Sie Abschnitt 3.2.3 in Doug Lea „Concurrent Programming in Java (Second Edition)“(Addison-Wesley, 2000) oder Artikel 50 in Joshua Blochs "Effective Java Programming Language Guide"(Addison-Wesley, 2001).

7

Der Hauptgrund, warum while Loops so wichtig sind, sind Race-Bedingungen zwischen Threads. Sicherlich sind weckende Wakeups real und für bestimmte Architekturen üblich, aber die Rennbedingungen sind ein viel wahrscheinlicherer Grund für die while Schleife.

Zum Beispiel:

synchronized (queue) { 
    // this needs to be while 
    while (queue.isEmpty()) { 
     queue.wait(); 
    } 
    queue.remove(); 
} 

Mit dem obigen Code, kann es 2 Verbraucher-Threads sein. Wenn der Hersteller die queue sperrt, um sie hinzuzufügen, kann Verbraucher Nr. 1 an der synchronized Sperre gelesen werden, um hineinzugehen, während Verbraucher # 2 bereits wartet. Wenn das Element der Warteschlange hinzugefügt wird und notify genannt, 2 # wird in der Warteschlange benachrichtigt und bewegt sich, für die queue Sperre warten, aber es ist hinter die # 1 Verbraucher, die bereits wartete. Dies bedeutet, dass der # 1-Verbraucher zuerst anrufen muss, um remove von queue anzurufen. Wenn die while Schleife nur ein if ist, dann, wenn die Verbraucher # 2 die Sperre bekommt und ruft remove, auftreten würde, eine Ausnahme, weil die queue leer war. Nur weil es gemeldet wurde, muss sichergestellt werden, dass der queue wegen dieser Wettlaufbedingung immer noch leer ist.

Dies ist gut dokumentiert. Hier ist eine Webseite, die ich vor einer Weile erstellt habe, die die race condition in detail erklärt und einen Beispielcode enthält.

+1

Sollte das nicht 'while (queue.isEmpty())' ohne '!' Sein? – user104309

+0

Ja, Sie haben Recht. Fest. Vielen Dank. – Gray

+0

Wie kommt es, dass der Verbraucher 1 so betrachtet werden kann, dass er aus der Warteschlange entfernt wird, wenn der Verbraucher 2 den synchronisierten Block nicht verlassen hat. Benachrichtigen wird sowieso den Verbraucher 2 benachrichtigen, und Verbraucher 1 wird untätig bleiben. –

1

Aus Ihrer Frage:

ich gelesen habe, dass wir immer eine Wartezeit() innerhalb einer Schleife aufgerufen sollte:

Obwohl wait() normalerweise wartet, bis notify() oder notifyAll () heißt es, es besteht die Möglichkeit, dass in sehr seltenen Fällen der wartende Faden durch ein ungewolltes Aufwecken aufgeweckt werden könnte. In diesem Fall wird ein wartender Thread fortgesetzt, ohne dass notify() oder notifyAll() aufgerufen wurde.

Im Wesentlichen nimmt der Faden ohne ersichtlichen Grund.

Aufgrund dieser entfernte Möglichkeit, empfiehlt Oracle, die (zu warten, ruft) sollte innerhalb einer Schleife nehmen, die die Bedingung überprüft, auf dem der Thread wartet.

+0

Diese Antwort ist falsch. Es ist nicht das Risiko von zufälligen Wakeups, deshalb sollte 'wait' in einer Schleife aufgerufen werden. Es liegt daran, dass ein anderer Thread die Bedingung behandelt hat. –

2

Sowohl die Sicherheit als auch die Verfügbarkeit sind bei der Verwendung des Wartungs-/Benachrichtigungsmechanismus von Bedeutung. Die Sicherheitseigenschaft erfordert, dass alle Objekte in einer Multithread-Umgebung konsistente Zustände beibehalten. Die Liveness-Eigenschaft erfordert, dass jeder Vorgang oder Methodenaufruf ohne Unterbrechung ausgeführt wird.

Vitalitäts zu gewährleisten, Programme müssen die while-Schleife Zustand testen, bevor die Methode wait() aufgerufen wird. Dieser frühe Test überprüft, ob ein anderer Thread das Bedingungsprädikat bereits erfüllt und eine Benachrichtigung gesendet hat. Das Aufrufen der Methode wait() nach dem Senden der Benachrichtigung führt zu einer unbestimmten Blockierung.

Um die Sicherheit zu gewährleisten, Programme müssen die while-Schleife Zustand getestet werden, danach von der Methode wait() zurückkehrt. Obwohl wait() soll auf unbestimmte Zeit blockieren, bis eine Meldung empfangen wird, muß es immer noch in einer Schleife eingeschlossen werden, um die folgenden Schwachstellen zu verhindern:

Gewinde in der Mitte: Ein dritte Thread die Sperre an dem gemeinsam genutzten erwerben Objekt während des Intervalls zwischen einer gesendeten Benachrichtigung und der Wiederaufnahme der Ausführung des empfangenden Threads. Dieser dritte Thread kann den Status des Objekts ändern und es inkonsistent lassen. Dies ist eine Race-Bedingung für den Zeitpunkt der Überprüfung, Nutzungszeit (TOCTOU).

Bösartige Benachrichtigung: Eine zufällige oder bösartige Benachrichtigung kann empfangen werden, wenn das Prädikat der Bedingung falsch ist. Eine solche Benachrichtigung würde die Methode wait() abbrechen.

Fehlgeleitete Benachrichtigung: Die Reihenfolge, in der Threads nach Empfang eines notifyAll() - Signals ausgeführt werden, ist nicht angegeben. Folglich kann ein nicht verwandter Thread mit der Ausführung beginnen und feststellen, dass sein Bedingungsprädikat erfüllt ist. Folglich könnte es die Ausführung fortsetzen, obwohl es erforderlich ist, ruhend zu bleiben.

Unechte Wakeups: Bestimmte Java Virtual Machine (JVM) Implementierungen sind anfällig für störende Wakeups, die auch ohne Benachrichtigung Aufwachen in wartenden Threads führen.

Aus diesen Gründen müssen Programme das Bedingungsprädikat überprüfen, nachdem die wait() -Methode zurückgegeben wurde. Eine while-Schleife ist die beste Wahl, um das Bedingungsprädikat vor und nach dem Aufruf von wait() zu überprüfen.

In ähnlicher Weise muss die await() - Methode der Condition-Schnittstelle auch innerhalb einer Schleife aufgerufen werden. Gemäß der Java-API-Schnittstelle Zustand

Wenn auf eine Bedingung wartet, wird eine „unechte Wakeup“ gestattet auftreten, in der Regel als Zugeständnis an die zugrunde liegende Plattform Semantik. Dies hat wenig praktische Auswirkungen auf die meisten Anwendungen Programme als eine Bedingung sollte immer in einer Schleife gewartet werden, Testen der Staat Prädikat, auf das gewartet wird. Eine Implementierung ist frei, um die Möglichkeit von unechten Wakeups zu entfernen, aber es wird empfohlen, dass Anwendungsprogrammierer immer davon ausgehen, dass sie auftreten können und so immer in einer Schleife warten.

Neuer Code sollte die java.util.concurrent.locks-Nebenläufigkeitsdienstprogramme anstelle des Wartungs-/Benachrichtigungsmechanismus verwenden. Legacycode, der den anderen Anforderungen dieser Regel entspricht, darf jedoch vom Wartungs-/Benachrichtigungsmechanismus abhängen.

Nicht kompatibel Codebeispiel Dieses noncompliant Codebeispiel ruft die Methode wait() in einem traditionellen, wenn Block und nicht die Nachbedingung zu überprüfen, nachdem die Benachrichtigung empfangen wird. Wenn die Benachrichtigung zufällig oder bösartig war, konnte der Thread vorzeitig aufwachen.

synchronized (object) { 
    if (<condition does not hold>) { 
    object.wait(); 
    } 
    // Proceed when condition holds 
} 

konforme Lösung Diese konforme Lösung ruft die wait() Methode aus einer while-Schleife um den Zustand zu prüfen, sowohl vor als auch nach dem Anruf warten():

synchronized (object) { 
    while (<condition does not hold>) { 
    object.wait(); 
    } 
    // Proceed when condition holds 
} 

Invocations von Die Methode java.util.concurrent.locks.Condition.await() muss ebenfalls in eine ähnliche Schleife eingeschlossen sein.

5

Ich denke, ich habe @Gray die Antwort bekommen.

Lassen Sie mich versuchen, das für Neulinge wie mich umzuformulieren und bitten Sie die Experten, mich zu korrigieren, wenn ich falsch liege.

Consumer synchronisierten Block::

synchronized (queue) { 
    // this needs to be while 
    while (queue.isEmpty()) { 
     queue.wait(); 
    } 
    queue.remove(); 
} 

Producer synchronisierten Block::

synchronized(queue) { 
// producer produces inside the queue 
    queue.notify(); 
} 

Angenommen, die folgenden in der angegebenen Reihenfolge geschieht:

1) Verbraucher # 2 kommt in den Verbraucher synchronized Block und wartet sinc Die Warteschlange ist leer.

2) Jetzt erhält Produzent die Sperre auf queue und fügt innerhalb der Warteschlange ein und ruft notify() auf.

nun entweder Verbraucher # 1 kann ausgeführt werden ausgewählt, die für queue Sperre wartet die synchronized Block zum ersten Mal

oder

Verbraucher 2 # laufen gewählt werden kann einzugeben.

3) sagen wir, Verbraucher # 1 wird gewählt, um mit der Ausführung fortzufahren. Wenn es die Bedingung überprüft, ist es wahr und es wird remove() aus der Warteschlange.

4) sagen, Verbraucher # 2 geht davon aus, wo es seine Ausführung angehalten hat (die Linie nach der wait() Methode). Wenn die 'while'-Bedingung nicht vorhanden ist (stattdessen eine if-Bedingung), wird einfach fortgefahren, um remove() aufzurufen, was zu einem Ausnahme-/unerwarteten Verhalten führen kann.

Verwandte Themen