Wenn Sie einen notify
beliebigen Thread im Wartesatz aktivieren, werden notifyAll
alle Threads im Wartesatz aktiviert. Die folgende Diskussion sollte alle Zweifel klären. notifyAll
sollte die meiste Zeit verwendet werden. Wenn Sie nicht sicher sind, welche Sie verwenden sollen, verwenden SienotifyAll
.Bitte lesen Sie die folgende Erklärung.
Lesen Sie sehr sorgfältig und verstehen Sie. Bitte senden Sie mir eine E-Mail, wenn Sie Fragen haben.
Schauen Sie sich Producer / Consumer an (Annahme ist eine ProducerConsumer-Klasse mit zwei Methoden). ES IST GEBROCHEN (weil es verwendet wird notify
) - ja, es kann funktionieren - sogar die meiste Zeit, aber es kann auch zu einem Deadlock führen - wir werden sehen warum:
public synchronized void put(Object o) {
while (buf.size()==MAX_SIZE) {
wait(); // called if the buffer is full (try/catch removed for brevity)
}
buf.add(o);
notify(); // called in case there are any getters or putters waiting
}
public synchronized Object get() {
// Y: this is where C2 tries to acquire the lock (i.e. at the beginning of the method)
while (buf.size()==0) {
wait(); // called if the buffer is empty (try/catch removed for brevity)
// X: this is where C1 tries to re-acquire the lock (see below)
}
Object o = buf.remove(0);
notify(); // called if there are any getters or putters waiting
return o;
}
ZUERST,
Warum brauchen wir eine while-Schleife, die das Warten umgibt?
Wir brauchen eine while
Schleife, falls wir diese Situation bekommen:
Verbraucher 1 (C1) betritt den synchronisierten Block und der Puffer ist leer, sodass C1 (über den wait
Aufruf) in den Wartesatz gesetzt wird . Consumer 2 (C2) ist dabei, die synchronisierte Methode einzugeben (am Punkt Y oben), aber Producer P1 legt ein Objekt in den Puffer und ruft anschließend aufnotify
. Der einzige wartende Thread ist C1, wird also geweckt und versucht nun, die Objektsperre am Punkt X (oben) wieder zu erlangen.
Jetzt versuchen C1 und C2, die Synchronisationssperre zu erlangen. Einer von ihnen (nicht deterministisch) wird ausgewählt und tritt in die Methode ein, der andere wird blockiert (nicht warten - sondern blockiert, um die Sperre für die Methode zu erlangen). Angenommen, C2 erhält zuerst die Sperre. C1 blockiert immer noch (versucht, die Sperre bei X zu erlangen). C2 schließt die Methode ab und gibt die Sperre frei. Jetzt erwirbt C1 das Schloss. Ratet mal, zum Glück haben wir eine while
Schleife, denn C1 führt die Schleifenprüfung (Guard) durch und verhindert, dass ein nicht vorhandenes Element aus dem Puffer entfernt wird (C2 hat es bereits!). Wenn wir keine hätten while
, würden wir eine bekommen, IndexArrayOutOfBoundsException
da C1 versucht, das erste Element aus dem Puffer zu entfernen!
JETZT,
Ok, warum brauchen wir jetzt notifyAll?
Im obigen Beispiel für Produzenten / Konsumenten sieht es so aus, als könnten wir damit durchkommen notify
. Es scheint so, weil wir , dass die Wachen auf den unter Beweis stellen können Warteschlaufen für Erzeuger und Verbraucher sich gegenseitig aus. Das heißt, es sieht so aus, als ob in der put
Methode und in der Methode kein Thread warten get
kann, denn damit dies wahr ist, müsste Folgendes wahr sein:
buf.size() == 0 AND buf.size() == MAX_SIZE
(Angenommen, MAX_SIZE ist nicht 0)
Dies ist jedoch nicht gut genug, wir müssen verwenden notifyAll
. Mal sehen warum ...
Angenommen, wir haben einen Puffer der Größe 1 (um das Beispiel einfach zu befolgen). Die folgenden Schritte führen uns zum Deadlock. Beachten Sie, dass JEDERZEIT ein Thread mit Benachrichtigung geweckt wird. Er kann von der JVM nicht deterministisch ausgewählt werden - das heißt, jeder wartende Thread kann geweckt werden. Beachten Sie auch, dass die Reihenfolge der Erfassung nicht deterministisch sein kann, wenn mehrere Threads beim Eintritt in eine Methode blockieren (dh beim Versuch, eine Sperre zu erhalten). Denken Sie auch daran, dass sich ein Thread immer nur in einer der Methoden befinden kann. Mit den synchronisierten Methoden kann nur ein Thread alle synchronisierten Methoden in der Klasse ausführen (dh die Sperre halten). Wenn die folgende Abfolge von Ereignissen auftritt, führt dies zu einem Deadlock:
SCHRITT 1:
- P1 legt 1 Zeichen in den Puffer
SCHRITT 2:
- P2-Versuche put
- Überprüft die Warteschleife - bereits ein Zeichen - wartet
SCHRITT 3:
- P3-Versuche put
- prüft die Warteschleife - bereits ein Zeichen - wartet
SCHRITT 4:
- C1 versucht, 1 Zeichen zu erhalten
- C2 versucht, 1 Zeichenblöcke beim Eintritt in die get
Methode zu erhalten
- C3 versucht, 1 Zeichenblöcke beim Eintritt in die get
Methode zu erhalten
SCHRITT 5:
- C1 führt die get
Methode aus - ruft das Zeichen ab, ruft auf notify
, beendet die Methode
- Das notify
Aufwecken P2
- ABER C2 gibt die Methode ein, bevor P2 kann (P2 muss die Sperre wiedererlangen), sodass P2 beim Eintritt in die put
Methode blockiert
- C2 prüft die Warte-Schleife, keine Zeichen mehr im Puffer, wartet also
- C3 gibt die Methode nach C2 ein, aber vor P2 prüft die Warte-Schleife, keine Zeichen mehr im Puffer, also wartet
SCHRITT 6:
- JETZT warten P3, C2 und C3!
- Schließlich erhält P2 die Sperre, legt ein Zeichen in den Puffer, ruft notify auf und beendet die Methode
SCHRITT 7:
- Die Benachrichtigung von P2 weckt P3 (denken Sie daran, dass jeder Thread geweckt werden kann)
- P3 überprüft den Zustand der Warteschleife. Der Puffer enthält bereits ein Zeichen und wartet.
- KEINE FADEN MEHR ZUM ANRUFEN UND DREI FADEN DAUERHAFT ANGEHÄNGT!
LÖSUNG: Ersetzen Sie durch notify
durch notifyAll
im Hersteller- / Verbrauchercode (oben).