Wenn man nach "Unterschied zwischen notify()
und notifyAll()
" googelt, werden viele Erklärungen angezeigt (wobei die Javadoc-Absätze außer Acht gelassen werden). Es läuft alles auf die Anzahl der wartenden Threads hinaus, die aufgeweckt werden: eins in notify()
und alles in notifyAll()
.
Wenn ich jedoch den Unterschied zwischen diesen Methoden richtig verstehe, wird immer nur ein Thread für die weitere Monitorerfassung ausgewählt. im ersten Fall die von der VM ausgewählte, im zweiten Fall die vom System-Thread-Scheduler ausgewählte. Die genauen Auswahlverfahren für beide (im allgemeinen Fall) sind dem Programmierer nicht bekannt.
Was ist der nützliche Unterschied zwischen notify () und notifyAll () dann? Vermisse ich etwas
java
multithreading
Sergey Mikhanov
quelle
quelle
synchronized
, mit Ausnahme bestimmter , eher seltene Anwendungsfälle.notifyAll()
Fall, dass in diesem Fall die anderen Threads nach dem ersten wach bleiben und den Monitor einzeln erfassen. In diesemnotify
Fall wird keiner der anderen Threads geweckt. Funktionell sind sie also sehr unterschiedlich!blocked
und nicht.waiting
Wennblocked
die Ausführung vorübergehend angehalten wird, bis sich ein anderer Thread imsync
Block befindet.Antworten:
Das ist nicht richtig.
o.notifyAll()
Aktiviert alle Threads, die ino.wait()
Aufrufen blockiert sind . Die Threads dürfen nur einzeln zurückkehreno.wait()
, aber jeder wird es tun an der Reihe.Einfach ausgedrückt, es hängt davon ab, warum Ihre Threads darauf warten, benachrichtigt zu werden. Möchten Sie einem der wartenden Threads mitteilen, dass etwas passiert ist, oder möchten Sie allen gleichzeitig mitteilen?
In einigen Fällen können alle wartenden Threads nach Abschluss des Wartens nützliche Maßnahmen ergreifen. Ein Beispiel wäre eine Reihe von Threads, die darauf warten, dass eine bestimmte Aufgabe abgeschlossen wird. Sobald die Aufgabe abgeschlossen ist, können alle wartenden Threads mit ihrem Geschäft fortfahren. In einem solchen Fall würden Sie notifyAll () verwenden. , um alle wartenden Threads gleichzeitig zu .
In einem anderen Fall, zum Beispiel beim sich gegenseitig ausschließenden Sperren, kann nur einer der wartenden Threads nach der Benachrichtigung etwas Nützliches tun (in diesem Fall die Sperre erwerben). In einem solchen Fall verwenden Sie lieber notify () . Richtig implementiert, Sie könnten verwenden notifyAll () auch in dieser Situation, aber Sie würden Fäden unnötig wecken , die nicht sowieso nichts tun kann.
In vielen Fällen wird der Code zum Warten auf eine Bedingung als Schleife geschrieben:
Auf diese Weise werden die anderen Threads, die aktiviert wurden, wieder gewartet , wenn ein
o.notifyAll()
Aufruf mehr als einen wartenden Thread weckt und der erste, der von deno.wait()
Marken zurückkehrt, den Zustand im falschen Zustand belässt.quelle
Wenn Sie einen
notify
beliebigen Thread im Wartesatz aktivieren, werdennotifyAll
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: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ättenwhile
, 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 derput
Methode und in der Methode kein Thread wartenget
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 - wartetSCHRITT 3:
- P3-Versuche
put
- prüft die Warteschleife - bereits ein Zeichen - wartetSCHRITT 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 erhaltenSCHRITT 5:
- C1 führt die
get
Methode aus - ruft das Zeichen ab, ruft aufnotify
, 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
durchnotifyAll
im Hersteller- / Verbrauchercode (oben).quelle
notify
dass P3 (der in diesem Beispiel ausgewählte Thread) von dem Punkt aus fortfährt, auf den es gewartet hat (dh innerhalb derwhile
Schleife). Es gibt andere Beispiele, die keinen Deadlock verursachen. In diesem Fallnotify
garantiert die Verwendung von jedoch keinen Deadlock-freien Code. Die Verwendung vonnotifyAll
tut.Nützliche Unterschiede:
Verwenden Sie notify (), wenn alle wartenden Threads austauschbar sind (die Reihenfolge, in der sie aktiviert werden, spielt keine Rolle) oder wenn Sie immer nur einen wartenden Thread haben. Ein häufiges Beispiel ist ein Thread-Pool, der zum Ausführen von Jobs aus einer Warteschlange verwendet wird. Wenn ein Job hinzugefügt wird, wird einer der Threads benachrichtigt, um aufzuwachen, den nächsten Job auszuführen und wieder in den Ruhezustand zu wechseln.
Verwenden Sie notifyAll () für andere Fälle, in denen die wartenden Threads möglicherweise andere Zwecke haben und gleichzeitig ausgeführt werden können. Ein Beispiel ist ein Wartungsvorgang für eine gemeinsam genutzte Ressource, bei dem mehrere Threads auf den Abschluss des Vorgangs warten, bevor sie auf die Ressource zugreifen.
quelle
Ich denke, es hängt davon ab, wie Ressourcen produziert und verbraucht werden. Wenn 5 Arbeitsobjekte gleichzeitig verfügbar sind und Sie 5 Consumer-Objekte haben, ist es sinnvoll, alle Threads mit notifyAll () zu aktivieren, damit jedes 1 Arbeitsobjekt verarbeiten kann.
Wenn Sie nur ein Arbeitsobjekt zur Verfügung haben, was bringt es, alle Consumer-Objekte zu aktivieren, um um dieses eine Objekt zu rennen? Der erste, der nach verfügbaren Arbeiten sucht, erhält diese und alle anderen Threads prüfen, ob sie nichts zu tun haben.
Ich habe hier eine gute Erklärung gefunden . Zusamenfassend:
quelle
Beachten Sie, dass Sie bei Parallelitätsdienstprogrammen auch die Wahl zwischen
signal()
und haben,signalAll()
da diese Methoden dort aufgerufen werden. Die Frage bleibt also auch bei gültigjava.util.concurrent
.Doug Lea spricht in seinem berühmten Buch einen interessanten Punkt an : Wenn a
notify()
und gleichzeitigThread.interrupt()
passieren, kann die Benachrichtigung tatsächlich verloren gehen. Wenn dies passieren kann und dramatische Auswirkungen hat,notifyAll()
ist dies eine sicherere Wahl, obwohl Sie den Overhead-Preis zahlen (meistens werden zu viele Threads geweckt).quelle
Kurze Zusammenfassung:
Ziehen Sie notifyAll () immer notify () vor , es sei denn, Sie haben eine massiv parallele Anwendung, bei der eine große Anzahl von Threads alle dasselbe tun.
Erläuterung:
Quelle: https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
Vergleichen Sie notify () mit notifyAll () in der oben beschriebenen Situation: eine massiv parallele Anwendung, in der Threads dasselbe tun. Wenn Sie in diesem Fall notifyAll () aufrufen, führt notifyAll () zum Aufwecken ( dh Planen) einer großen Anzahl von Threads, von denen viele unnötig sind (da nur ein Thread tatsächlich fortfahren kann, nämlich der Thread, dem der gewährt wird Monitor für das Objekt wait () , notify () oder notifyAll () wurde auf) genannt, also Rechenressourcen zu verschwenden.
Wenn Sie also nicht über eine Anwendung, wo eine große Anzahl von Threads gleichzeitig das gleiche tun, bevorzugen notifyAll () über () mitteilen . Warum? Weil, wie andere Benutzer bereits in diesem Forum geantwortet haben, notify ()
Quelle: Java SE8 API ( https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify-- )
Stellen Sie sich vor, Sie haben eine Hersteller-Verbraucheranwendung, bei der Verbraucher zum Konsumieren bereit sind (dh warten () ), Produzenten zum Produzieren bereit sind (dh warten () ) und die Warteschlange der zu produzierenden / zu konsumierenden Artikel leer ist. In diesem Fall weckt notify () möglicherweise nur Verbraucher und niemals Produzenten, da die Wahl, wer geweckt wird, willkürlich ist . Der Konsumzyklus der Produzenten würde keine Fortschritte machen, obwohl Produzenten und Konsumenten bereit sind, zu produzieren bzw. zu konsumieren. Stattdessen wird ein Verbraucher aufgeweckt (dh der Status wait () wird verlassen ), nimmt ein Element nicht aus der Warteschlange, weil es leer ist, und benachrichtigt () einen anderen Verbraucher, um fortzufahren.
Im Gegensatz dazu weckt notifyAll () sowohl Produzenten als auch Konsumenten. Die Auswahl, wer geplant ist, hängt vom Planer ab. Abhängig von der Implementierung des Schedulers kann der Scheduler natürlich auch nur Consumer planen (z. B. wenn Sie Consumer-Threads eine sehr hohe Priorität zuweisen). Hierbei wird jedoch davon ausgegangen, dass die Gefahr, dass der Scheduler nur Verbraucher plant, geringer ist als die Gefahr, dass die JVM nur Verbraucher aufweckt, da ein vernünftig implementierter Scheduler nicht nur willkürliche Entscheidungen trifft. Vielmehr unternehmen die meisten Scheduler-Implementierungen zumindest einige Anstrengungen, um einen Hunger zu verhindern.
quelle
Hier ist ein Beispiel. Starte es. Ändern Sie dann eines der notifyAll () in notify () und sehen Sie, was passiert.
ProducerConsumerExample-Klasse
Dropbox-Klasse
Verbraucherklasse
Produzentenklasse
quelle
Von Joshua Bloch, dem Java Guru selbst in der 2. Ausgabe von Effective Java:
"Punkt 69: Bevorzugen Sie Parallelitätsdienstprogramme, um zu warten und zu benachrichtigen".
quelle
Ich hoffe, dies wird einige Zweifel beseitigen.
notify () : Die Methode notify () aktiviert einen Thread, der auf die Sperre wartet (den ersten Thread, der wait () für diese Sperre aufgerufen hat).
notifyAll () : Die Methode notifyAll () aktiviert alle Threads, die auf die Sperre warten. Die JVM wählt einen der Threads aus der Liste der Threads aus, die auf die Sperre warten, und aktiviert diesen Thread.
Wenn ein einzelner Thread auf eine Sperre wartet, gibt es keinen signifikanten Unterschied zwischen notify () und notifyAll (). Wenn jedoch sowohl in notify () als auch in notifyAll () mehr als ein Thread auf die Sperre wartet, wird der genaue aufgeweckte Thread von der JVM gesteuert, und Sie können das Aufwecken eines bestimmten Threads nicht programmgesteuert steuern.
Auf den ersten Blick scheint es eine gute Idee zu sein, einfach notify () aufzurufen, um einen Thread aufzuwecken. Es scheint unnötig, alle Threads aufzuwecken. Das Problem mit notify () besteht jedoch darin, dass der aufgeweckte Thread möglicherweise nicht zum Aufwecken geeignet ist (der Thread wartet möglicherweise auf eine andere Bedingung oder die Bedingung ist für diesen Thread usw. immer noch nicht erfüllt). In diesem Fall geht notify () möglicherweise verloren und kein anderer Thread wird möglicherweise aktiviert, was zu einer Art Deadlock führt (die Benachrichtigung geht verloren und alle anderen Threads warten auf Benachrichtigung - für immer).
Um dieses Problem zu vermeiden , ist es immer besser, notifyAll () aufzurufen, wenn mehr als ein Thread auf eine Sperre wartet (oder mehr als eine Bedingung, unter der gewartet wird). Die notifyAll () -Methode aktiviert alle Threads und ist daher nicht sehr effizient. Dieser Leistungsverlust ist jedoch in realen Anwendungen vernachlässigbar.
quelle
Es gibt drei Zustände für einen Thread.
Wenn nun notify () aufgerufen wird, wählt JVM einen Thread aus und versetzt ihn in den Status BLOCKED und damit in den Status RUNNING, da für das Monitorobjekt keine Konkurrenz besteht.
Wenn ein notifyAll () aufgerufen wird, wählt JVM alle Threads aus und versetzt sie alle in den BLOCKED-Status. Alle diese Threads erhalten die Sperre des Objekts auf Prioritätsbasis. Thread, der zuerst den Monitor erfassen kann, kann zuerst in den Status RUNNING wechseln und so weiter.
quelle
Ich bin sehr überrascht, dass niemand das berüchtigte "Lost Wakeup" -Problem erwähnt hat (google it).
Grundsätzlich:
DANN sollten Sie notifyAll verwenden, es sei denn, Sie haben nachweisliche Garantien, dass verlorene Weckvorgänge unmöglich sind.
Ein häufiges Beispiel ist eine gleichzeitige FIFO-Warteschlange, bei der: Mehrere Warteschlangen (1. und 3. oben) Ihre Warteschlange von leer auf nicht leer umstellen können. Mehrere Warteschlangen (2. oben) können auf die Bedingung warten, dass die Warteschlange nicht leer ist -> Nicht leer sollten Dequeuers benachrichtigen
Sie können leicht eine Verschachtelung von Operationen schreiben, bei der ausgehend von einer leeren Warteschlange 2 Enqueuer und 2 Dequeuer interagieren und 1 Enqueuer im Ruhezustand bleibt.
Dies ist ein Problem, das wohl mit dem Deadlock-Problem vergleichbar ist.
quelle
notify()
wird einen ThreadnotifyAll()
aufwecken, während alle aufgeweckt werden. Soweit ich weiß, gibt es keinen Mittelweg. Wenn Sie sich jedoch nicht sicher sind, wasnotify()
mit Ihren Threads geschehen soll, verwenden SienotifyAll()
. Funktioniert jedes Mal wie ein Zauber.quelle
Alle obigen Antworten sind richtig, soweit ich das beurteilen kann, also werde ich Ihnen noch etwas anderes sagen. Für Produktionscode sollten Sie wirklich die Klassen in java.util.concurrent verwenden. Es gibt sehr wenig, was sie nicht für Sie tun können, im Bereich der Parallelität in Java.
quelle
notify()
Mit dieser Option können Sie effizienteren Code schreiben alsnotifyAll()
.Betrachten Sie den folgenden Code, der von mehreren parallelen Threads ausgeführt wird:
Es kann effizienter gemacht werden durch
notify()
:In dem Fall, wenn Sie eine große Anzahl von Threads haben oder wenn die Auswertung der Warteschleifenbedingung kostspielig ist,
notify()
ist dies erheblich schneller alsnotifyAll()
. Wenn Sie beispielsweise 1000 Threads haben, werden 999 Threads nach dem erstennotifyAll()
, dann 998, dann 997 usw. aktiviert und ausgewertet . Im Gegenteil, mit dernotify()
Lösung wird nur ein Thread geweckt.Verwenden
notifyAll()
Sie diese Option, wenn Sie auswählen müssen, welcher Thread als Nächstes die Arbeit erledigen soll:Schließlich ist es wichtig zu verstehen, dass im Falle von
notifyAll()
, dass der Code insynchronized
den aufgeweckten Blöcken nacheinander ausgeführt wird, nicht alle auf einmal. Angenommen, im obigen Beispiel warten drei Threads, und der vierte Thread ruft aufnotifyAll()
. Alle drei Threads werden aktiviert, aber nur einer startet die Ausführung und überprüft den Zustand derwhile
Schleife. Wenn die Bedingung erfüllt isttrue
, wird siewait()
erneut aufgerufen , und erst dann wird der zweite Thread ausgeführt und überprüft seinewhile
Schleifenbedingung usw.quelle
Hier ist eine einfachere Erklärung:
Sie haben Recht, dass unabhängig davon, ob Sie notify () oder notifyAll () verwenden, das unmittelbare Ergebnis ist, dass genau ein anderer Thread den Monitor erfasst und mit der Ausführung beginnt. (Angenommen, einige Threads wurden tatsächlich beim Warten () auf dieses Objekt blockiert, andere nicht verwandte Threads saugen nicht alle verfügbaren Kerne usw. auf.) Die Auswirkungen treten später auf.
Angenommen, Thread A, B und C haben auf dieses Objekt gewartet, und Thread A erhält den Monitor. Der Unterschied liegt darin, was passiert, wenn A den Monitor freigibt. Wenn Sie notify () verwendet haben, sind B und C immer noch in wait () blockiert: Sie warten nicht auf dem Monitor, sondern darauf, benachrichtigt zu werden. Wenn A den Monitor freigibt, sitzen B und C immer noch dort und warten auf eine Benachrichtigung ().
Wenn Sie notifyAll () verwendet haben, haben B und C den Status "Warten auf Benachrichtigung" überschritten und warten beide darauf, den Monitor zu erhalten. Wenn A den Monitor freigibt, wird er entweder von B oder C erfasst (vorausgesetzt, keine anderen Threads konkurrieren um diesen Monitor) und mit der Ausführung beginnen.
quelle
Diese Antwort ist eine grafische Umschreibung und Vereinfachung der hervorragenden Antwort von xagyg , einschließlich der Kommentare von eran .
Warum notifyAll verwenden, auch wenn jedes Produkt für einen einzelnen Verbraucher bestimmt ist?
Betrachten Sie Produzenten und Konsumenten, vereinfacht wie folgt.
Hersteller:
Verbraucher:
Angenommen, 2 Produzenten und 2 Konsumenten teilen sich einen Puffer der Größe 1. Das folgende Bild zeigt ein Szenario, das zu a führt Deadlock führt , der vermieden würde, wenn alle verwendeten Threads notifyAll verwenden .
Jede Benachrichtigung ist mit dem aufgeweckten Thread gekennzeichnet.
quelle
Ich möchte erwähnen, was in Java Concurrency in Practice erklärt wird:
Erster Punkt, ob Notify oder NotifyAll?
Dieses Problem kann durch Verwendung des in jdk 5 bereitgestellten Bedingungsobjekts der expliziten Sperrung gelöst werden, da es für jedes Bedingungsprädikat unterschiedliche Wartezeiten bietet. Hier verhält es sich korrekt und es tritt kein Leistungsproblem auf, da das Signal aufgerufen wird und sichergestellt wird, dass nur ein Thread auf diesen Zustand wartet
quelle
notify()
- Wählt einen zufälligen Thread aus dem Wartesatz des Objekts aus und legt ihn in derBLOCKED
Status. Der Rest der Threads im Wartesatz des Objekts befindet sich noch imWAITING
Status.notifyAll()
- Verschiebt alle Threads aus dem Wartesatz des Objekts in denBLOCKED
Status. Nachdem Sie verwendet habennotifyAll()
befinden sich keine Threads mehr im Wartesatz des freigegebenen Objekts, da sich alle jetzt imBLOCKED
Status und nicht imWAITING
Status befinden.BLOCKED
- für den Erwerb der Sperre gesperrt.WAITING
- Warten auf Benachrichtigung (oder Blockieren für den Abschluss des Beitritts).quelle
Entnommen aus einem Blog über Effective Java:
Also, was ich verstehe ist (aus dem oben genannten Blog, Kommentar von "Yann TM" zu akzeptierten Antworten und Java- Dokumenten ):
quelle
Schauen Sie sich den Code von @xagyg an.
Angenommen, zwei verschiedene Threads warten auf zwei verschiedene Bedingungen:
Der erste Thread wartet auf
buf.size() != MAX_SIZE
und der zweite Thread wartet aufbuf.size() != 0
.Angenommen, irgendwann
buf.size()
ist ungleich 0 . JVM ruftnotify()
stattdessen aufnotifyAll()
und der erste Thread wird benachrichtigt (nicht der zweite).Der erste Thread wird aufgeweckt, prüft, auf
buf.size()
welchen er zurückkehren könnteMAX_SIZE
, und wartet wieder. Der zweite Thread wird nicht aufgeweckt, wartet weiter und ruft nicht anget()
.quelle
notify()
Aktiviert den ersten Thread,wait()
der dasselbe Objekt aufgerufen hat .notifyAll()
Aktiviert alle Threads,wait()
die dasselbe Objekt aufgerufen haben .Der Thread mit der höchsten Priorität wird zuerst ausgeführt.
quelle
notify()
es nicht genau " der erste Thread ".notify benachrichtigt nur einen Thread, der sich im Wartezustand befindet, während notify all alle Threads im Wartezustand benachrichtigt, jetzt sind alle benachrichtigten Threads und alle blockierten Threads für die Sperre berechtigt, von denen nur einer die Sperre erhält und Alle anderen (einschließlich derjenigen, die sich früher im Wartezustand befinden) befinden sich im blockierten Zustand.
quelle
Um die hervorragenden detaillierten Erklärungen oben zusammenzufassen und auf die einfachste Art und Weise, die ich mir vorstellen kann, ist dies auf die Einschränkungen des eingebauten JVM-Monitors zurückzuführen, der 1) auf der gesamten Synchronisationseinheit (Block oder Objekt) erfasst wird und 2) unterscheidet nicht über die spezifische Bedingung, auf die gewartet / benachrichtigt wird.
Dies bedeutet, dass, wenn mehrere Threads unter verschiedenen Bedingungen warten und notify () verwendet wird, der ausgewählte Thread möglicherweise nicht derjenige ist, der Fortschritte bei der neu erfüllten Bedingung erzielen würde - was dazu führt, dass dieser Thread (und andere derzeit noch wartende Threads, die dies können) um die Bedingung zu erfüllen, etc ..) nicht in der Lage zu sein, Fortschritte zu machen, und schließlich Hunger oder Programm hängen.
Im Gegensatz dazu ermöglicht notifyAll () allen wartenden Threads, die Sperre schließlich wieder zu erlangen und auf ihren jeweiligen Zustand zu prüfen, wodurch schließlich Fortschritte erzielt werden können.
Notify () kann daher nur dann sicher verwendet werden, wenn garantiert ist, dass ein wartender Thread Fortschritte erzielt, wenn er ausgewählt wird. Dies ist im Allgemeinen erfüllt, wenn alle Threads innerhalb desselben Monitors nur auf ein und dieselbe Bedingung prüfen - eine ziemlich seltene Fall in realen Anwendungen.
quelle
Wenn Sie wait () des "Objekts" aufrufen (in der Erwartung, dass die Objektsperre erfasst wird), wird durch intern die Sperre für dieses Objekt aufgehoben und den anderen Threads geholfen, dieses "Objekt" zu sperren. In diesem Szenario wird dies der Fall sein Es wartet mehr als ein Thread auf die "Ressource / das Objekt" (unter Berücksichtigung der anderen Threads, die ebenfalls auf dasselbe Objekt oben gewartet haben, und auf dem Weg dorthin gibt es einen Thread, der die Ressource / das Objekt füllt und notify / notifyAll aufruft).
Wenn Sie hier die Benachrichtigung über dasselbe Objekt (von derselben / anderer Seite des Prozesses / Codes) ausgeben, wird ein blockierter und wartender einzelner Thread freigegeben (nicht alle wartenden Threads - dieser freigegebene Thread wird von JVM-Thread ausgewählt Der Scheduler und der gesamte Prozess zum Abrufen der Sperre für das Objekt sind identisch mit dem regulären.
Wenn Sie nur einen Thread haben, der dieses Objekt freigibt / bearbeitet, ist es in Ordnung, die notify () -Methode allein in Ihrer Wait-Notify-Implementierung zu verwenden.
Wenn Sie sich in einer Situation befinden, in der mehr als ein Thread basierend auf Ihrer Geschäftslogik Ressourcen / Objekte liest und schreibt, sollten Sie sich für notifyAll () entscheiden.
Jetzt schaue ich, wie genau das JVM den wartenden Thread identifiziert und unterbricht, wenn wir notify () für ein Objekt ausgeben ...
quelle
Obwohl es oben einige solide Antworten gibt, bin ich überrascht über die Anzahl der Verwirrungen und Missverständnisse, die ich gelesen habe. Dies beweist wahrscheinlich die Idee, dass man java.util.concurrent so oft wie möglich verwenden sollte, anstatt zu versuchen, eigenen kaputten gleichzeitigen Code zu schreiben. Zurück zur Frage: Zusammenfassend lässt sich sagen, dass die beste Vorgehensweise heute darin besteht, notify () in ALLEN Situationen aufgrund des Problems des verlorenen Aufweckens zu vermeiden. Wer dies nicht versteht, sollte keinen geschäftskritischen Parallelitätscode schreiben dürfen. Wenn Sie sich Sorgen über das Hüteproblem machen, können Sie sicher einen Thread aufwecken, indem Sie: 1. eine explizite Warteschlange für die wartenden Threads erstellen; 2. Lassen Sie jeden Thread in der Warteschlange auf seinen Vorgänger warten. 3. Lassen Sie jeden Thread notifyAll () aufrufen, wenn Sie fertig sind. Oder Sie können Java.util.concurrent verwenden. *,
quelle
Runnable
Implementierung) den Inhalt einer Warteschlange verarbeitet. Daswait()
wird dann verwendet, wenn die Warteschlange leer ist. Und dasnotify()
wird aufgerufen, wenn Informationen hinzugefügt werden. -> In einem solchen Fall gibt es nur 1 Thread, der jemals den aufruftwait()
. Dann sieht es nicht ein bisschen albern aus, einen zu verwenden,notifyAll()
wenn Sie wissen, dass nur 1 Thread wartet.Alles aufzuwachen spielt hier keine große Rolle. Warten Sie, benachrichtigen Sie und benachrichtigen Sie alle, alle diese werden nach dem Besitz des Objektmonitors gesetzt. Wenn sich ein Thread in der Wartephase befindet und eine Benachrichtigung aufgerufen wird, nimmt dieser Thread die Sperre auf und kein anderer Thread an diesem Punkt kann diese Sperre übernehmen. Ein gleichzeitiger Zugriff kann also überhaupt nicht stattfinden. Soweit ich weiß, kann ein Anruf zum Warten von Benachrichtigung und Benachrichtigung nur erfolgen, nachdem das Objekt gesperrt wurde. Korrigieren Sie mich, wenn ich falsch liege.
quelle