Java: notify () vs. notifyAll () noch einmal


377

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


6
Die nützlichen Bibliotheken für die Parallelität befinden sich in den Parallelitätsbibliotheken. Ich schlage vor, dass dies in fast allen Fällen eine bessere Wahl ist. Die Concurency-Bibliothek datiert vor Java 5.0 (in dem sie 2004 standardmäßig hinzugefügt wurden)
Peter Lawrey

4
Ich bin nicht einverstanden mit Peter. Die Gleichzeitigkeit Bibliothek ist in Java implementiert, und es gibt eine Menge von Java - Code jedes Mal , wenn Lockruf ausgeführt (), unlock (), etc. Sie selbst in den Fuß schießen kann durch Gleichzeitigkeit Bibliothek anstelle von guten alten synchronized, mit Ausnahme bestimmter , eher seltene Anwendungsfälle.
Alexander Ryzhov

2
Das Hauptmissverständnis scheint folgendes zu sein: ... es 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 Implikation ist, dass die im Wesentlichen gleich sind. Während das beschriebene Verhalten korrekt ist, fehlt in diesem notifyAll()Fall, dass in diesem Fall die anderen Threads nach dem ersten wach bleiben und den Monitor einzeln erfassen. In diesem notifyFall wird keiner der anderen Threads geweckt. Funktionell sind sie also sehr unterschiedlich!
BeeOnRope

1) Wenn viele Threads auf ein Objekt warten und notify () nur einmal für dieses Objekt aufgerufen wird. Außer einem der wartenden Threads warten die verbleibenden Threads für immer? 2) Wenn notify () verwendet wird, wird nur einer von vielen wartenden Threads ausgeführt. Wenn notifyall () verwendet wird, werden alle wartenden Threads benachrichtigt, aber nur einer von ihnen wird ausgeführt. Wozu dient notifyall () hier?
Chetan Gowda

@ChetanGowda Alle Threads benachrichtigen vs Nur genau einen beliebigen Thread benachrichtigen weist tatsächlich einen signifikanten Unterschied auf, bis uns dieser scheinbar subtile, aber wichtige Unterschied auffällt. Wenn Sie nur einen Thread benachrichtigen (), befinden sich alle anderen Threads im Wartezustand, bis eine explizite Benachrichtigung empfangen wird /Signal. Wenn Sie alle benachrichtigen, werden alle Threads ohne weitere Benachrichtigung nacheinander in einer bestimmten Reihenfolge ausgeführt und abgeschlossen. Hier sollten wir sagen, dass es sich um Threads handelt blockedund nicht. waitingWenn blockeddie Ausführung vorübergehend angehalten wird, bis sich ein anderer Thread im syncBlock befindet.
user104309

Antworten:


248

Wenn ich jedoch den Unterschied zwischen diesen Methoden richtig verstehe, wird immer nur ein Thread für die weitere Monitorerfassung ausgewählt.

Das ist nicht richtig. o.notifyAll()Aktiviert alle Threads, die in o.wait()Aufrufen blockiert sind . Die Threads dürfen nur einzeln zurückkehren o.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:

synchronized(o) {
    while (! IsConditionTrue()) {
        o.wait();
    }
    DoSomethingThatOnlyMakesSenseWhenConditionIsTrue_and_MaybeMakeConditionFalseAgain();
}

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 den o.wait()Marken zurückkehrt, den Zustand im falschen Zustand belässt.


29
Wenn Sie nur einen Thread benachrichtigen, aber mehrere auf ein Objekt warten, wie bestimmt die VM, welcher benachrichtigt werden soll?
Amphibient

6
Ich kann nicht sicher über die Java-Spezifikation sagen, aber im Allgemeinen sollten Sie vermeiden, Annahmen über solche Details zu treffen. Ich denke, Sie können davon ausgehen, dass die VM dies auf vernünftige und größtenteils faire Weise tun wird.
Liedman

15
Liedman ist schwerwiegend falsch, die Java-Spezifikation besagt ausdrücklich, dass notify () nicht garantiert fair ist. dh jeder Aufruf zur Benachrichtigung weckt möglicherweise denselben Thread erneut (die Thread-Warteschlange im Monitor ist NICHT FAIR oder FIFO). Der Planer ist jedoch garantiert fair. Aus diesem Grund sollten Sie in den meisten Fällen, in denen Sie mehr als 2 Threads haben, notifyAll bevorzugen.
Yann TM

45
@YannTM Ich bin alles für konstruktive Kritik, aber ich denke, dein Ton ist ein bisschen unfair. Ich sagte ausdrücklich "kann nicht sicher sagen" und "ich denke". Einfach gesagt, haben Sie vor sieben Jahren jemals etwas geschrieben, das nicht 100% korrekt war?
Liedman

10
Das Problem ist, dass dies die akzeptierte Antwort ist, es ist keine Frage des persönlichen Stolzes. Wenn Sie wissen, dass Sie sich jetzt geirrt haben, bearbeiten Sie bitte Ihre Antwort, um sie zu sagen, und verweisen Sie unten auf z. B. xagyg pädagogische und korrekte Antwort.
Yann TM

330

Wenn Sie einen notifybeliebigen Thread im Wartesatz aktivieren, werden notifyAllalle Threads im Wartesatz aktiviert. Die folgende Diskussion sollte alle Zweifel klären. notifyAllsollte 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 whileSchleife, falls wir diese Situation bekommen:

Verbraucher 1 (C1) betritt den synchronisierten Block und der Puffer ist leer, sodass C1 (über den waitAufruf) 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 whileSchleife, 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, IndexArrayOutOfBoundsExceptionda 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 putMethode und in der Methode kein Thread warten getkann, 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 getMethode zu erhalten
- C3 versucht, 1 Zeichenblöcke beim Eintritt in die getMethode zu erhalten

SCHRITT 5:
- C1 führt die getMethode aus - ruft das Zeichen ab, ruft auf notify, beendet die Methode
- Das notifyAufwecken P2
- ABER C2 gibt die Methode ein, bevor P2 kann (P2 muss die Sperre wiedererlangen), sodass P2 beim Eintritt in die putMethode 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 notifydurch notifyAllim Hersteller- / Verbrauchercode (oben).


1
finnw - P3 muss den Zustand erneut überprüfen, da dies dazu führt, notifydass P3 (der in diesem Beispiel ausgewählte Thread) von dem Punkt aus fortfährt, auf den es gewartet hat (dh innerhalb der whileSchleife). Es gibt andere Beispiele, die keinen Deadlock verursachen. In diesem Fall notifygarantiert die Verwendung von jedoch keinen Deadlock-freien Code. Die Verwendung von notifyAlltut.
Xagyg

4
@ Marcus Sehr nah. Mit notifyAll erhält jeder Thread die Sperre erneut (einzeln). Beachten Sie jedoch, dass der nächste Thread die Sperre erneut abruft und das "while" überprüft, nachdem ein Thread die Sperre erneut erhalten und die Methode ausgeführt (und dann beendet) hat. und geht zurück zu "warten" (abhängig von der Bedingung natürlich). Benachrichtigen Sie also, dass ein Thread aktiviert wird - wie Sie richtig angeben. notifyAll weckt alle Threads und jeder Thread ruft die Sperre einzeln wieder auf - überprüft den Zustand des "while" und führt entweder die Methode aus oder "wartet" erneut.
Xagyg

1
@ xagyg, sprichst du von einem Szenario, in dem jeder Produzent nur ein einziges Zeichen zu speichern hat? Wenn ja, ist Ihr Beispiel richtig, aber nicht sehr interessant IMO. Mit den zusätzlichen Schritten, die ich vorschlage, können Sie dasselbe System blockieren, jedoch mit einer unbegrenzten Menge an Eingaben - so werden solche Muster normalerweise in der Realität verwendet.
Eran

3
@codeObserver Sie haben gefragt: "Würde der Aufruf von notifyAll () dazu führen, dass mehrere wartende Threads gleichzeitig die while () -Zustand prüfen. Daher besteht die Möglichkeit, dass bereits 2 Threads vorhanden sind, bevor die while-Funktion aktiviert wird, was outOfBound verursacht Ausnahme? " Nein, dies ist nicht möglich, da zwar mehrere Threads aufwachen würden, sie jedoch nicht gleichzeitig die while-Bedingung überprüfen können. Sie müssen jeweils die Sperre (unmittelbar nach dem Warten) wieder erreichen, bevor sie den Codeabschnitt erneut aufrufen und die Zeit erneut überprüfen können. Daher einer nach dem anderen.
Xagyg

4
@ xagyg schönes Beispiel. Dies ist kein Thema von der ursprünglichen Frage; nur zur Diskussion. Der Deadlock ist ein Designproblem imo (korrigiere mich, wenn ich falsch liege). Weil Sie eine Sperre haben, die sowohl von put als auch von get geteilt wird. Und JVM ist nicht klug genug, um Put anzurufen, nachdem das Schloss freigegeben wurde und umgekehrt. Die tote Sperre tritt auf, weil put einen anderen Put aufweckt, der sich aufgrund von while () zurücksetzt, um zu warten (). Würde es funktionieren, zwei Klassen (und zwei Schlösser) zu erstellen? Also setze {synchonized (get)}, get {(synchonized (put)}. Mit anderen Worten, get wird nur put put und put wird nur get get.
Jay

43

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.


19

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:

Die notify () -Methode wird im Allgemeinen für Ressourcenpools verwendet , bei denen eine beliebige Anzahl von "Verbrauchern" oder "Arbeitern" Ressourcen beansprucht. Wenn jedoch eine Ressource zum Pool hinzugefügt wird, kann nur einer der wartenden Verbraucher oder Arbeitnehmer handeln damit. Die notifyAll () -Methode wird in den meisten anderen Fällen tatsächlich verwendet. Streng genommen ist es erforderlich, Kellner über eine Bedingung zu informieren, die es mehreren Kellnern ermöglichen könnte, fortzufahren. Dies ist jedoch oft schwer zu wissen. So in der Regel, wenn Sie keine bestimmte Logik benachrichtigen für die Verwendung von (), dann sollten Sie wahrscheinlich verwenden notifyAll () , weil es oft schwierig ist , genau zu wissen , was Threads auf einem bestimmten Objekt warten und warum.


11

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ültig java.util.concurrent.

Doug Lea spricht in seinem berühmten Buch einen interessanten Punkt an : Wenn a notify()und gleichzeitig Thread.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).


10

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:

notify () [...] weckt einen einzelnen Thread. Da Sie mit notify () den aufgeweckten Thread nicht angeben können, ist er nur in massiv parallelen Anwendungen nützlich, dh in Programmen mit einer großen Anzahl von Threads, die alle ähnliche Aufgaben ausführen. In einer solchen Anwendung ist es Ihnen egal, welcher Thread aufgeweckt wird.

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 ()

Aktiviert einen einzelnen Thread, der auf dem Monitor dieses Objekts wartet. [...] Die Auswahl ist willkürlich und liegt im Ermessen der Implementierung.

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.


9

Hier ist ein Beispiel. Starte es. Ändern Sie dann eines der notifyAll () in notify () und sehen Sie, was passiert.

ProducerConsumerExample-Klasse

public class ProducerConsumerExample {

    private static boolean Even = true;
    private static boolean Odd = false;

    public static void main(String[] args) {
        Dropbox dropbox = new Dropbox();
        (new Thread(new Consumer(Even, dropbox))).start();
        (new Thread(new Consumer(Odd, dropbox))).start();
        (new Thread(new Producer(dropbox))).start();
    }
}

Dropbox-Klasse

public class Dropbox {

    private int number;
    private boolean empty = true;
    private boolean evenNumber = false;

    public synchronized int take(final boolean even) {
        while (empty || evenNumber != even) {
            try {
                System.out.format("%s is waiting ... %n", even ? "Even" : "Odd");
                wait();
            } catch (InterruptedException e) { }
        }
        System.out.format("%s took %d.%n", even ? "Even" : "Odd", number);
        empty = true;
        notifyAll();

        return number;
    }

    public synchronized void put(int number) {
        while (!empty) {
            try {
                System.out.println("Producer is waiting ...");
                wait();
            } catch (InterruptedException e) { }
        }
        this.number = number;
        evenNumber = number % 2 == 0;
        System.out.format("Producer put %d.%n", number);
        empty = false;
        notifyAll();
    }
}

Verbraucherklasse

import java.util.Random;

public class Consumer implements Runnable {

    private final Dropbox dropbox;
    private final boolean even;

    public Consumer(boolean even, Dropbox dropbox) {
        this.even = even;
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            dropbox.take(even);
            try {
                Thread.sleep(random.nextInt(100));
            } catch (InterruptedException e) { }
        }
    }
}

Produzentenklasse

import java.util.Random;

public class Producer implements Runnable {

    private Dropbox dropbox;

    public Producer(Dropbox dropbox) {
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            int number = random.nextInt(10);
            try {
                Thread.sleep(random.nextInt(100));
                dropbox.put(number);
            } catch (InterruptedException e) { }
        }
    }
}

8

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".


16
Das Warum ist wichtiger als die Quelle.
Pacerier

2
@ Pacerier Gut gesagt. Ich wäre auch mehr daran interessiert, die Gründe herauszufinden. Ein möglicher Grund könnte sein, dass Warten und Benachrichtigen in der Objektklasse auf einer impliziten Bedingungsvariablen basieren. Im Standardbeispiel für Hersteller und Verbraucher ..... warten sowohl Hersteller als auch Verbraucher unter denselben Bedingungen, die zu einem Deadlock führen können, wie von xagyg in seiner Antwort erläutert. Ein besserer Ansatz ist es daher, zwei Bedingungsvariablen zu verwenden, wie unter docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/…
rahul erläutert

6

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.


6

Es gibt drei Zustände für einen Thread.

  1. WAIT - Der Thread verwendet keinen CPU-Zyklus
  2. BLOCKED - Der Thread ist blockiert, um einen Monitor zu erhalten. Möglicherweise werden die CPU-Zyklen weiterhin verwendet
  3. RUNNING - Der Thread wird ausgeführt.

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.


Einfach tolle Erklärung.
Royatirek

5

Ich bin sehr überrascht, dass niemand das berüchtigte "Lost Wakeup" -Problem erwähnt hat (google it).

Grundsätzlich:

  1. Wenn mehrere Threads unter denselben Bedingungen warten und
  2. Mehrere Threads, mit denen Sie von Status A zu Status B wechseln können.
  3. mehrere Threads, mit denen Sie von Status B in Status A übergehen können (normalerweise dieselben Threads wie in 1.) und,
  4. Beim Übergang von Zustand A nach B sollten Threads in 1 benachrichtigt werden.

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.


Ich entschuldige mich, xagyg erklärt es ausführlich. Der Name für das Problem ist "Lost Wakeup"
NickV

@Abhay Bansal: Ich denke, Sie vermissen die Tatsache, dass condition.wait () die Sperre aufhebt und sie von dem Thread, der aufwacht, wiedererlangt wird.
NickV

4

notify()wird einen Thread notifyAll()aufwecken, während alle aufgeweckt werden. Soweit ich weiß, gibt es keinen Mittelweg. Wenn Sie sich jedoch nicht sicher sind, was notify()mit Ihren Threads geschehen soll, verwenden Sie notifyAll(). Funktioniert jedes Mal wie ein Zauber.


4

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.


4

notify()Mit dieser Option können Sie effizienteren Code schreiben als notifyAll().

Betrachten Sie den folgenden Code, der von mehreren parallelen Threads ausgeführt wird:

synchronized(this) {
    while(busy) // a loop is necessary here
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    notifyAll();
}

Es kann effizienter gemacht werden durch notify():

synchronized(this) {
    if(busy)   // replaced the loop with a condition which is evaluated only once
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    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 als notifyAll(). Wenn Sie beispielsweise 1000 Threads haben, werden 999 Threads nach dem ersten notifyAll(), dann 998, dann 997 usw. aktiviert und ausgewertet . Im Gegenteil, mit der notify()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:

synchronized(this) {
    while(idx != last+1)  // wait until it's my turn
        wait();
}
...
synchronized(this) {
    last = idx;
    notifyAll();
}

Schließlich ist es wichtig zu verstehen, dass im Falle von notifyAll(), dass der Code in synchronizedden aufgeweckten Blöcken nacheinander ausgeführt wird, nicht alle auf einmal. Angenommen, im obigen Beispiel warten drei Threads, und der vierte Thread ruft auf notifyAll(). Alle drei Threads werden aktiviert, aber nur einer startet die Ausführung und überprüft den Zustand der whileSchleife. Wenn die Bedingung erfüllt ist true, wird sie wait()erneut aufgerufen , und erst dann wird der zweite Thread ausgeführt und überprüft seine whileSchleifenbedingung usw.


4

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.


Sehr klare Erklärung. Das Ergebnis dieses Verhaltens von notify () kann zu "Missed Signal" / "Missed Notification" führen, was zu einem Deadlock / keiner Fortschrittssituation des Anwendungsstatus führt. P-Produzent, C-Consumer P1, P2 und C2 warten auf C1. C1 ruft notify () auf und ist für einen Produzenten gedacht, aber C2 kann aufgeweckt werden, sodass sowohl P1 als auch P2 die Benachrichtigung verpasst haben und auf eine weitere explizite "Benachrichtigung" warten (ein notify () -Aufruf).
user104309

4

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:

while (!empty) {
   wait() // on full
}
put()
notify()

Verbraucher:

while (empty) {
   wait() // on empty
}
take()
notify()

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.

Deadlock wegen Benachrichtigung


3

Ich möchte erwähnen, was in Java Concurrency in Practice erklärt wird:

Erster Punkt, ob Notify oder NotifyAll?

It will be NotifyAll, and reason is that it will save from signall hijacking.

Wenn zwei Threads A und B auf unterschiedliche Bedingungsprädikate derselben Bedingungswarteschlange warten und notify aufgerufen wird, liegt es an JVM, an welchen Thread JVM benachrichtigt.

Wenn nun Benachrichtigung für Thread A und JVM-Benachrichtigungsthread B gedacht war, wird Thread B aktiviert und stellt fest, dass diese Benachrichtigung nicht nützlich ist, sodass er erneut wartet. Und Thread A wird nie von diesem verpassten Signal erfahren und jemand hat seine Benachrichtigung entführt.

Wenn Sie also notifyAll aufrufen, wird dieses Problem behoben, aber auch dies hat Auswirkungen auf die Leistung, da alle Threads benachrichtigt werden und alle Threads um dieselbe Sperre konkurrieren und ein Kontextwechsel und damit eine Belastung der CPU erforderlich sind. Wir sollten uns jedoch nur dann um die Leistung kümmern, wenn sie sich korrekt verhält. Wenn das Verhalten selbst nicht korrekt ist, nützt die Leistung nichts.

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


3

notify() - Wählt einen zufälligen Thread aus dem Wartesatz des Objekts aus und legt ihn in der BLOCKED Status. Der Rest der Threads im Wartesatz des Objekts befindet sich noch im WAITINGStatus.

notifyAll()- Verschiebt alle Threads aus dem Wartesatz des Objekts in den BLOCKEDStatus. Nachdem Sie verwendet habennotifyAll() befinden sich keine Threads mehr im Wartesatz des freigegebenen Objekts, da sich alle jetzt im BLOCKEDStatus und nicht im WAITINGStatus befinden.

BLOCKED- für den Erwerb der Sperre gesperrt. WAITING- Warten auf Benachrichtigung (oder Blockieren für den Abschluss des Beitritts).


3

Entnommen aus einem Blog über Effective Java:

The notifyAll method should generally be used in preference to notify. 

If notify is used, great care must be taken to ensure liveness.

Also, was ich verstehe ist (aus dem oben genannten Blog, Kommentar von "Yann TM" zu akzeptierten Antworten und Java- Dokumenten ):

  • notify (): JVM weckt einen der wartenden Threads für dieses Objekt. Die Fadenauswahl erfolgt willkürlich ohne Fairness. So kann derselbe Thread immer wieder geweckt werden. Der Status des Systems ändert sich also, aber es werden keine wirklichen Fortschritte erzielt. So entsteht ein Viehbestand .
  • notifyAll (): JVM weckt alle Threads und dann rennen alle Threads um die Sperre für dieses Objekt. Jetzt wählt der CPU-Scheduler einen Thread aus, der die Sperre für dieses Objekt erhält. Dieser Auswahlprozess wäre viel besser als die Auswahl durch JVM. So wird Lebendigkeit gewährleistet.

2

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_SIZEund der zweite Thread wartet aufbuf.size() != 0 .

Angenommen, irgendwann buf.size() ist ungleich 0 . JVM ruft notify()stattdessen auf notifyAll()und der erste Thread wird benachrichtigt (nicht der zweite).

Der erste Thread wird aufgeweckt, prüft, auf buf.size()welchen er zurückkehren könnte MAX_SIZE, und wartet wieder. Der zweite Thread wird nicht aufgeweckt, wartet weiter und ruft nicht an get().


1

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.


13
Im Falle ist notify()es nicht genau " der erste Thread ".
Bhesh Gurung

6
Sie können nicht vorhersagen, welche von VM ausgewählt wird. Das weiß nur Gott.
Sergii Shevchyk

Es gibt keine Garantie, wer der Erste sein wird (keine Fairness)
Ivan Voroshilin

Der erste Thread wird nur aktiviert, wenn das Betriebssystem dies garantiert, und es ist wahrscheinlich, dass dies nicht der Fall ist. Es verzichtet wirklich auf das Betriebssystem (und seinen Scheduler), um zu bestimmen, welcher Thread aufgeweckt werden soll.
Paul Stelian

1

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.


1

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.


0

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 ...


0

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. *,


Nach meiner Erfahrung wird die Verwendung von Warten / Benachrichtigen häufig in Warteschlangenmechanismen verwendet, in denen ein Thread ( RunnableImplementierung) den Inhalt einer Warteschlange verarbeitet. Das wait()wird dann verwendet, wenn die Warteschlange leer ist. Und das notify()wird aufgerufen, wenn Informationen hinzugefügt werden. -> In einem solchen Fall gibt es nur 1 Thread, der jemals den aufruft wait(). Dann sieht es nicht ein bisschen albern aus, einen zu verwenden, notifyAll()wenn Sie wissen, dass nur 1 Thread wartet.
bvdb

-2

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.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.