Frage

Wenn man nach „Unterschied zwischen“ googelt notify() Und notifyAll()" Dann werden viele Erklärungen auftauchen (die Javadoc-Absätze abgesehen).Es läuft alles auf die Anzahl der wartenden Threads hinaus, die aufgeweckt werden:einer in notify() und alles drin notifyAll().

Allerdings wird (wenn ich den Unterschied zwischen diesen Methoden richtig verstehe) immer nur ein Thread für die weitere Monitorerfassung ausgewählt;im ersten Fall der von der VM ausgewählte, im zweiten Fall der vom System-Thread-Scheduler ausgewählte.Die genauen Auswahlverfahren für beide (im allgemeinen Fall) sind dem Programmierer nicht bekannt.

Was ist das? nützlich Unterschied zwischen benachrichtigen() Und notifyAll() Dann?Vermisse ich etwas?

War es hilfreich?

Lösung

Allerdings wird (wenn ich den Unterschied zwischen diesen Methoden richtig verstehe) immer nur ein Thread für die weitere Monitorerfassung ausgewählt.

Das ist nicht richtig. o.notifyAll() erwacht alle der Threads, die blockiert sind o.wait() Anrufe.Die Threads dürfen nur von zurückkehren o.wait() Einer nach dem anderen, aber sie jeder Wille kommen an die Reihe.


Einfach ausgedrückt hängt es davon ab, warum Ihre Threads auf eine Benachrichtigung warten.Möchten Sie einem der wartenden Threads mitteilen, dass etwas passiert ist, oder möchten Sie allen gleichzeitig mitteilen?

In manchen Fällen können alle wartenden Threads nützliche Maßnahmen ergreifen, sobald die Wartezeit beendet ist.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 verwenden notifyAll() um alle wartenden Threads gleichzeitig aufzuwecken.

In einem anderen Fall, beispielsweise bei sich gegenseitig ausschließender Sperre, 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 benachrichtigen().Richtig umgesetzt, Sie könnte verwenden notifyAll() auch in dieser Situation, aber Sie würden unnötigerweise Threads wecken, die sowieso nichts tun können.


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, wenn ein o.notifyAll() Der Aufruf weckt mehr als einen wartenden Thread und den ersten, der von dem zurückkehrt o.wait() Belässt die Bedingung im falschen Zustand, dann werden die anderen Threads, die aktiviert wurden, wieder warten.

Andere Tipps

Deutlich, notify weckt (beliebigen) einen Thread im Wartesatz, notifyAll weckt alle Threads im Wartesatz.Die folgende Diskussion soll eventuelle Zweifel ausräumen. notifyAll sollte die meiste Zeit verwendet werden.Wenn Sie nicht sicher sind, welches Sie verwenden sollen, dann verwenden Sie notifyAll.Bitte beachten Sie die folgende Erklärung.

Sehr sorgfältig lesen und verstehen.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 KAPUTT (weil es verwendet wird). notify) – ja, es KANN funktionieren – meistens sogar, 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 rund um das Warten?

Wir brauchen ein while Schleife für den Fall, dass wir diese Situation bekommen:

Verbraucher 1 (C1) betritt den synchronisierten Block und der Puffer ist leer, sodass C1 in den Wartesatz eingefügt wird (über wait Anruf).Verbraucher 2 (C2) ist dabei, die synchronisierte Methode aufzurufen (an Punkt Y oben), aber Produzent P1 legt ein Objekt in den Puffer und ruft es anschließend auf notify.Der einzige wartende Thread ist C1, daher wird er aktiviert und versucht nun, die Objektsperre an Punkt X (oben) erneut zu erlangen.

Jetzt versuchen C1 und C2, die Synchronisationssperre zu erlangen.Einer von ihnen (nichtdeterministisch) wird ausgewählt und tritt in die Methode ein, der andere wird blockiert (wartet nicht, sondern blockiert und versucht, die Sperre für die Methode zu erlangen).Nehmen wir an, 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 erhält C1 die Sperre.Ratet mal, zum Glück haben wir einen while Schleife, da C1 die Schleifenprüfung durchführt (Guard) und daran gehindert wird, ein nicht vorhandenes Element aus dem Puffer zu entfernen (C2 hat es bereits erhalten!).Wenn wir keins hätten while, wir würden eine bekommen IndexArrayOutOfBoundsException als C1 versucht, das erste Element aus dem Puffer zu entfernen!

JETZT,

Ok, warum brauchen wir jetzt notifyAll?

Im obigen Produzenten-/Konsumenten-Beispiel sieht es so aus, als könnten wir damit durchkommen notify.Es scheint so, denn wir können beweisen, dass die Wachen auf dem Warten Schleifen für Producer und Consumer schließen sich gegenseitig aus.Das heißt, es sieht so aus, als ob kein Thread in der warten kann put Methode sowie die get Methode, denn damit das 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 es nutzen notifyAll.Mal sehen, warum ...

Gehen wir davon aus, dass wir einen Puffer der Größe 1 haben (damit das Beispiel leichter verständlich ist).Die folgenden Schritte führen uns zum Stillstand.Beachten Sie, dass ein Thread JEDERZEIT, wenn er mit notify geweckt wird, von der JVM nichtdeterministisch ausgewählt werden kann – das heißt, jeder wartende Thread kann geweckt werden.Beachten Sie außerdem, dass, wenn mehrere Threads den Zugriff auf eine Methode blockieren (z. B.Beim Versuch, eine Sperre zu erwerben, kann die Reihenfolge des Erwerbs nicht deterministisch sein.Denken Sie auch daran, dass sich ein Thread jeweils nur in einer der Methoden befinden kann – die synchronisierten Methoden erlauben nur die Ausführung eines Threads (d. h.Halten der Sperre aller (synchronisierten) Methoden in der Klasse.Wenn die folgende Abfolge von Ereignissen auftritt, kommt es zu einem Deadlock:

SCHRITT 1:
- P1 legt 1 Zeichen in den Puffer

SCHRITT 2:
- P2-Versuche put - prü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 Zeichen zu erhalten - blockiert beim Eintritt in das get Methode
- C3 versucht, 1 Zeichen zu erhalten - blockiert beim Eintritt in das get Methode

SCHRITT 5:
- C1 führt das aus get Methode – erhält das Zeichen, ruft auf notify, beendet die Methode
- Der notify weckt P2
- ABER C2 tritt in die Methode ein, bevor P2 dies kann (P2 muss die Sperre erneut erlangen), daher blockiert P2 den Zugriff auf die Methode put Methode
– C2 prüft die Warteschleife, es sind keine weiteren Zeichen im Puffer, also wartet
– C3 tritt nach C2 in die Methode ein, prüft aber vor P2 die Warteschleife, keine weiteren Zeichen im Puffer, also wartet

SCHRITT 6:
- JETZT:Da 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 prüft die Warteschleifenbedingung, es gibt bereits ein Zeichen im Puffer, also wartet.
- KEINE Threads mehr zum Aufrufen von Notify und drei Threads dauerhaft ausgesetzt!

LÖSUNG:Ersetzen notify mit notifyAll im Producer/Consumer-Code (oben).

Nützliche Unterschiede:

  • Verwenden benachrichtigen() wenn alle Ihre 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, aufzuwachen, den nächsten Job auszuführen und wieder in den Ruhezustand zu wechseln.

  • Verwenden notifyAll() für andere Fälle, in denen die wartenden Threads möglicherweise unterschiedliche Zwecke haben und gleichzeitig ausgeführt werden können sollten.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.

Ich denke, es hängt davon ab, wie Ressourcen produziert und verbraucht werden.Wenn 5 Arbeitsobjekte gleichzeitig verfügbar sind und Sie 5 Verbraucherobjekte haben, wäre es sinnvoll, alle Threads mit notifyAll() aufzuwecken, damit jeder 1 Arbeitsobjekt verarbeiten kann.

Wenn Sie nur ein Arbeitsobjekt zur Verfügung haben, welchen Sinn hat es dann, alle Verbraucherobjekte zu wecken, um um dieses eine Objekt zu rennen?Der erste Thread, der nach verfügbarer Arbeit sucht, erhält sie und alle anderen Threads prüfen und stellen fest, dass sie nichts zu tun haben.

Ich habe einen ... gefunden tolle Erklärung hier.Zusamenfassend:

Die melden () -Methode wird im Allgemeinen für verwendet Ressourcenpools, wo es eine willkürliche Anzahl von "Verbrauchern" oder "Arbeitnehmern" gibt, die Ressourcen aufnehmen. Wenn der Pool jedoch eine Ressource hinzugefügt wird, können nur einer der wartenden Verbraucher oder Arbeitnehmer damit umgehen.In den meisten anderen Fällen wird die meldenALL () -Methode tatsächlich verwendet.Strengstens ist es erforderlich, Kellner über eine Bedingung zu informieren, die es mehreren Kellnern ermöglichen könnte, fortzufahren.Aber das ist oft schwer zu wissen.Also in der Regel, in der Regel, Wenn Sie keine bestimmte Logik für die Verwendung von Notify () haben, sollten Sie wahrscheinlich BenoyifyAll () verwenden., weil es oft schwierig ist, genau zu wissen, welche Threads auf ein bestimmtes Objekt warten und warum.

Beachten Sie, dass Sie bei Parallelitätsdienstprogrammen auch die Wahl zwischen haben signal() Und signalAll() wie diese Methoden dort genannt werden.Die Frage bleibt also auch mit gültig java.util.concurrent.

Doug Lea bringt in seinem Beitrag einen interessanten Punkt zur Sprache berühmtes Buch:wenn ein notify() Und Thread.interrupt() Wenn gleichzeitig ein Fehler auftritt, kann es sein, dass die Benachrichtigung tatsächlich verloren geht.Wenn dies passieren kann und dramatische Auswirkungen hat notifyAll() ist eine sicherere Wahl, auch wenn Sie den Preis für den Overhead zahlen (meistens werden zu viele Threads aktiviert).

Von Joshua Bloch, dem Java-Guru selbst in der 2. Auflage von Effective Java:

„Punkt 69:Bevorzugen Sie Parallelitätsdienstprogramme zum Warten und Benachrichtigen.

Hier ist ein Beispiel.Starte es.Ändern Sie dann eine der notifyAll()-Anweisungen 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) { }
        }
    }
}

Kurze Zusammenfassung:

Immer bevorzugen notifyAll() über benachrichtigen() es sei denn, Sie haben eine massiv parallele Anwendung, bei der eine große Anzahl von Threads alle dasselbe tun.

Erläuterung:

benachrichtigen() ...] wacht einen einzelnen Thread auf.Weil benachrichtigen() Ermöglicht es Ihnen nicht, den aufgeweckten Thread anzugeben. Er ist nur in massiv parallelen Anwendungen nützlich - das heißt, Programme mit einer großen Anzahl von Threads, die alle ähnliche Aufgaben erledigen.In einer solchen Anwendung ist es Ihnen egal, welcher Thread aktiviert wird.

Quelle: https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

Vergleichen benachrichtigen() mit notifyAll() in der oben beschriebenen Situation:eine massiv parallele Anwendung, bei der Threads dasselbe tun.Wenn Sie anrufen notifyAll() In diesem Fall, notifyAll() wird das Aufwachen herbeiführen (d. h.Scheduling) einer großen Anzahl von Threads, viele davon unnötig (da nur ein Thread tatsächlich fortfahren kann, nämlich der Thread, dem die Überwachung für das Objekt gewährt wird). Warten(), benachrichtigen(), oder notifyAll() aufgerufen wurde) und somit Rechenressourcen verschwendet.

Wenn Sie also keine Anwendung haben, in der eine große Anzahl von Threads gleichzeitig dasselbe tun, bevorzugen Sie notifyAll() über benachrichtigen().Warum?Denn wie andere Benutzer in diesem Forum bereits geantwortet haben, benachrichtigen()

weckt einen einzelnen Thread, der auf dem Monitor dieses Objekts wartet....] Die Wahl ist willkürlich und tritt nach Ermessen der Umsetzung auf.

Quelle:Java SE8-API (https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify--)

Stellen Sie sich vor, Sie haben eine Producer-Consumer-Anwendung, bei der die Consumer bereit sind (d. h. Warten() ing) zu konsumieren, sind die Produzenten bereit (d. h. Warten() ing) zu produzieren und die Warteschlange der Artikel (zu produzieren / zu konsumieren) ist leer.In diesem Fall, benachrichtigen() Möglicherweise werden nur die Verbraucher und niemals die Produzenten geweckt, denn die Wahl liegt darin, wer geweckt wird willkürlich.Der Produzenten-Konsumenten-Zyklus würde keine Fortschritte machen, obwohl Produzenten und Konsumenten bereit sind, zu produzieren bzw. zu konsumieren.Stattdessen wird ein Verbraucher geweckt (d. h.etwas verlassen Warten() Status), nimmt ein Element nicht aus der Warteschlange, weil es leer ist, und benachrichtigen() Es ist ein weiterer Verbraucher, der fortfahren muss.

Im Gegensatz, notifyAll() weckt sowohl Produzenten als auch Verbraucher.Die Wahl, wer eingeplant wird, hängt vom Planer ab.Abhängig von der Implementierung des Schedulers kann der Scheduler natürlich auch nur Verbraucher planen (z. B.wenn Sie Consumer-Threads eine sehr hohe Priorität zuweisen).Allerdings wird hier davon ausgegangen, dass die Gefahr, dass der Scheduler nur Verbraucher plant, geringer ist als die Gefahr, dass die JVM nur Verbraucher aufweckt, weil jeder vernünftig implementierte Scheduler dies nicht einfach macht willkürlich Entscheidungen.Vielmehr unternehmen die meisten Scheduler-Implementierungen zumindest einige Anstrengungen, um eine Hungersnot zu verhindern.

Ich bin sehr überrascht, dass niemand das berüchtigte Problem des „verlorenen Aufwachens“ erwähnt hat (google es).

Grundsätzlich:

  1. wenn mehrere Threads auf dieselbe Bedingung warten und
  2. mehrere Threads, die Ihnen den Übergang von Zustand A zu Zustand B ermöglichen können, und
  3. mehrere Threads, die Ihnen den Übergang von Zustand B zu Zustand A ermöglichen können (normalerweise die gleichen Threads wie in 1.) und,
  4. Der Übergang von Zustand A nach B sollte Threads in 1 benachrichtigen.

DANN sollten Sie notifyAll verwenden, es sei denn, Sie haben nachweisbare Garantien dafür, dass verlorene Weckrufe unmöglich sind.

Ein häufiges Beispiel ist eine gleichzeitige FIFO-Warteschlange, bei der:mehrere Enqueuer (1.und 3.Oben) kann Ihre Warteschlange von leer zu nicht leerem Mehrfach dequeuers (2.oben) kann auf die Bedingung warten "Die Warteschlange ist nicht leer" leer -> Nicht -leer sollte Dequeuers benachrichtigen

Sie können ganz einfach eine Verschachtelung von Operationen schreiben, bei der ausgehend von einer leeren Warteschlange zwei Enqueuer und zwei Dequeuer interagieren und ein Enqueuer im Ruhezustand bleibt.

Dies ist ein Problem, das wohl mit dem Deadlock-Problem vergleichbar ist.

Ich hoffe, dass dies einige Zweifel ausräumen wird.

benachrichtigen() :Die Benachrichtigung () -Methode weckt einen Thread auf, der auf das Schloss wartet (der erste Thread, der in diesem Schloss Wait () bezeichnete).

notifyAll() :Die notifyAll()-Methode weckt alle Threads, die auf die Sperre warten;Der JVM wählt einen der Threads aus der Liste der Threads aus und wartet auf die Sperre und weckt diesen Thread hoch.

Im Falle eines einzelnen Threads Beim Warten auf eine Sperre 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 genau der Thread aktiviert unter der Kontrolle der JVM 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 könnte unnötig erscheinen, alle Threads aufzuwecken.Allerdings ist das Problem mit notify() bedeutet, dass der aufgeweckte Thread möglicherweise nicht der passende ist aufgeweckt werden (der Thread wartet möglicherweise auf eine andere Bedingung, oder die Bedingung ist für diesen Thread immer noch nicht erfüllt usw.). In diesem Fall, könnte notify() verloren gehen und kein anderer Thread wird aktiviert, was möglicherweise 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, auf die gewartet wird).Die Methode notifyAll() weckt alle Threads und ist daher nicht sehr effizient.In realen Anwendungen ist dieser Leistungsverlust jedoch vernachlässigbar.

notify() wird währenddessen einen Thread aufwecken notifyAll() wird alle aufwecken.Soweit ich weiß, gibt es keinen Mittelweg.Aber wenn Sie nicht sicher sind, was notify() wird mit deinen Threads zu tun haben, verwenden notifyAll().Funktioniert jedes Mal wie ein Zauber.

Soweit ich das beurteilen kann, sind alle oben genannten Antworten richtig, deshalb werde ich Ihnen noch etwas anderes sagen.Für Produktionscode sollten Sie unbedingt die Klassen in java.util.concurrent verwenden.Es gibt sehr wenig, was sie im Bereich der Parallelität in Java nicht für Sie tun können.

Hier ist eine einfachere Erklärung:

Sie haben Recht, dass unabhängig davon, ob Sie notify() oder notifyAll() verwenden, das unmittelbare Ergebnis darin besteht, dass genau ein anderer Thread den Monitor erhält und mit der Ausführung beginnt.(Angenommen, einige Threads wurden tatsächlich bei wait() für dieses Objekt blockiert, andere nicht verwandte Threads verbrauchen nicht alle verfügbaren Kerne usw.) Die Auswirkung kommt später.

Angenommen, Thread A, B und C warten auf dieses Objekt und Thread A erhält den Monitor.Der Unterschied liegt darin, was passiert, wenn A den Monitor loslässt.Wenn Sie notify() verwendet haben, werden B und C immer noch in wait() blockiert:Sie warten nicht auf dem Monitor, sie warten darauf, benachrichtigt zu werden.Wenn A den Monitor freigibt, sitzen B und C immer noch dort und warten auf eine notify().

Wenn Sie notifyAll() verwendet haben, haben B und C beide den Status „Warten auf Benachrichtigung“ überschritten und warten beide darauf, den Monitor abzurufen.Wenn A den Monitor freigibt, wird entweder B oder C ihn erwerben (vorausgesetzt, dass keine anderen Threads um diesen Monitor konkurrieren) und mit der Ausführung beginnen.

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

  1. WAIT – Der Thread verwendet keinen CPU-Zyklus
  2. BLOCKIERT – Der Thread ist beim Versuch, einen Monitor abzurufen, blockiert. Er nutzt möglicherweise immer noch die CPU-Zyklen
  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 es keine Konkurrenz für das Monitorobjekt gibt.

Wenn notifyAll() aufgerufen wird, wählt JVM alle Threads aus und versetzt sie alle in den Status BLOCKED.Alle diese Threads erhalten die Sperre des Objekts auf Prioritätsbasis. Threads, die zuerst den Monitor abrufen können, können zuerst in den Status „RUNNING“ wechseln und so weiter.

Diese Antwort ist eine grafische Umschreibung und Vereinfachung der hervorragenden Antwort von xagyg, einschließlich Kommentare von Eran.

Warum notifyAll verwenden, auch wenn jedes Produkt für einen einzelnen Verbraucher bestimmt ist?

Betrachten Sie Produzenten und Verbraucher, vereinfacht wie folgt.

Hersteller:

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

Verbraucher:

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

Angenommen, zwei Produzenten und zwei Verbraucher teilen sich einen Puffer der Größe 1.Das folgende Bild zeigt ein Szenario, das zu a führt Sackgasse, was vermieden würde, wenn alle Threads verwendet würden notifyAll.

Jede Benachrichtigung ist mit dem Thread gekennzeichnet, der aktiviert wird.

deadlock due to notify

notify() lässt Sie effizienteren Code schreiben als notifyAll().

Betrachten Sie den folgenden Codeabschnitt, 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 durch die Verwendung effizienter gemacht werden notify():

synchronized(this) {
    if(busy)   // replaced the loop with a condition which is evaluated only once
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    notify();
}

Wenn Sie über eine große Anzahl von Threads verfügen oder die Auswertung der Warteschleifenbedingung kostspielig ist, notify() wird deutlich schneller sein als notifyAll().Wenn Sie beispielsweise 1000 Threads haben, werden nach dem ersten 999 Threads aktiviert und ausgewertet notifyAll(), dann 998, dann 997 und so weiter.Im Gegenteil, mit dem notify() Lösung wird nur ein Thread aktiviert.

Verwenden notifyAll() 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, das zu verstehen notifyAll(), der Code darin synchronized Blöcke, die aktiviert wurden, werden nacheinander und nicht alle auf einmal ausgeführt.Nehmen wir an, im obigen Beispiel warten drei Threads und der vierte Thread ruft auf notifyAll().Alle drei Threads werden aktiviert, aber nur einer beginnt mit der Ausführung und überprüft den Zustand des Threads while Schleife.Wenn die Bedingung ist true, es wird anrufen wait() erneut, und erst dann beginnt der zweite Thread mit der Ausführung und überprüft seine Ausführung while Schleifenbedingung usw.

Genommen von Blog zu effektivem Java:

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

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

Was ich also verstehe, ist (aus dem oben genannten Blog, Kommentar von „Yann TM“ auf akzeptierte Antwort und Java Dokumente):

  • notify() :JVM weckt einen der wartenden Threads für dieses Objekt.Die Themenauswahl erfolgt willkürlich und ohne Fairness.So kann derselbe Thread immer wieder aktiviert werden.Der Zustand des Systems ändert sich also, es werden jedoch keine wirklichen Fortschritte erzielt.So entsteht ein Livelock.
  • 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 eine Sperre für dieses Objekt erhält.Dieser Auswahlprozess wäre viel besser als die Auswahl durch JVM.So ist für Lebendigkeit gesorgt.

Schauen Sie sich den von @xagyg geposteten Code an.

Angenommen, zwei verschiedene Threads warten auf zwei unterschiedliche Bedingungen:
Der erster Thread wartet auf buf.size() != MAX_SIZE, und das zweiter Thread wartet auf buf.size() != 0.

Angenommen, irgendwann buf.size() ist ungleich 0.JVM-Aufrufe notify() anstatt notifyAll(), und der erste Thread wird benachrichtigt (nicht der zweite).

Der erste Thread wird aufgeweckt und überprüft buf.size() was zurückkehren könnte MAX_SIZE, und kehrt zum Warten zurück.Der zweite Thread wird nicht aufgeweckt, wartet weiter und ruft nicht auf get().

notify() weckt den ersten Thread, der aufgerufen hat wait() am selben Objekt.

notifyAll() weckt alle aufgerufenen Threads auf wait() am selben Objekt.

Der Thread mit der höchsten Priorität wird zuerst ausgeführt.

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 Zustandsprädikate derselben Bedingung warten und die Benachrichtigung aufgerufen wird, ist es bis zu JVM, anhand derer Thread JVM benachrichtigt wird.

Wenn die Benachrichtigung nun für Thread A und JVM benachrichtigt wurde, und JVM -Thread B, wird Thread B aufwacht und feststellen, dass diese Benachrichtigung nicht nützlich ist, sodass es erneut wartet.Und Thread A wird nie von diesem verpassten Signal erfahren, und jemand entführte es ist eine Benachrichtigung.

Das Aufrufen von NotifyAll wird dieses Problem beheben, aber es hat auch die Leistungserbringung, da es alle Threads benachrichtigt und alle Threads um die gleiche Sperre konkurrieren und einen Kontextschalter und damit die Last auf der CPU beinhalten.Aber wir sollten uns nur um die Leistung kümmern, wenn sie sich richtig verhält, wenn das Verhalten selbst nicht korrekt ist, ist die Leistung nicht nützlich.

Dieses Problem kann durch die Verwendung des Bedingungsobjekts der expliziten Sperrung Lock gelöst werden, das in JDK 5 bereitgestellt wird, da es für jedes Bedingungsprädikat unterschiedliche Wartezeiten bereitstellt.Hier verhält es sich korrekt und es treten keine Leistungsprobleme auf, da es ein Signal aufruft und sicherstellt, dass nur ein Thread auf diese Bedingung wartet

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 zuvor im Wartezustand befanden) befinden sich im blockierten Zustand.

notify() – Wählt einen zufälligen Thread aus der Wartemenge des Objekts aus und fügt ihn in die ein BLOCKED Zustand.Die restlichen Threads im Wartesatz des Objekts befinden sich noch im WAITING Zustand.

notifyAll() – Verschiebt alle Threads aus der Wartemenge des Objekts nach BLOCKED Zustand.Nach der Verwendung notifyAll(), es sind keine Threads mehr im Wartesatz des gemeinsam genutzten Objekts vorhanden, da sie sich jetzt alle darin befinden BLOCKED Staat und nicht in WAITING Zustand.

BLOCKED - Für Sperrenerfassung gesperrt.WAITING - Warten auf Benachrichtigung (oder blockiert für den Abschluss des Beitritts).

Um die hervorragenden detaillierten Erklärungen oben zusammenzufassen, und zwar auf die einfachste Art und Weise, die ich mir vorstellen kann, liegt dies an den Einschränkungen des integrierten JVM-Monitors, der 1) auf der gesamten Synchronisierungseinheit (Block oder Objekt) erfasst wird und 2) macht keinen Unterschied hinsichtlich der spezifischen Bedingung, auf die gewartet/benachrichtigt wird.

Das bedeutet, dass, wenn mehrere Threads auf unterschiedliche Bedingungen warten und notify() verwendet wird, der ausgewählte Thread möglicherweise nicht derjenige ist, der bei der neu erfüllten Bedingung Fortschritte machen würde – was dazu führt, dass dieser Thread (und andere derzeit noch wartende Threads, die dazu in der Lage wären). um die Bedingung zu erfüllen usw.), nicht in der Lage zu sein, Fortschritte zu machen, und schließlich verhungert das Programm oder hängt sich auf.

Im Gegensatz dazu ermöglicht notifyAll() allen wartenden Threads, die Sperre schließlich erneut zu erhalten und ihren jeweiligen Zustand zu überprüfen, wodurch schließlich Fortschritte erzielt werden können.

Daher kann notify() nur dann sicher verwendet werden, wenn ein wartender Thread garantiert einen Fortschritt zulässt, 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 – was ziemlich selten vorkommt Fall in realen Anwendungen.

Wenn Sie „wait()“ des „Objekts“ aufrufen (in der Erwartung, dass die Objektsperre erworben wird), wird dies intern die Sperre für dieses Objekt aufheben und den anderen Threads helfen, eine Sperre für dieses „Objekt“ zu haben. In diesem Szenario wird dies der Fall sein mehr als ein Thread wartet auf die „Ressource/das Objekt“ (wenn man bedenkt, dass die anderen Threads ebenfalls auf dasselbe obige Objekt gewartet haben und es dann einen Thread geben wird, der die Ressource/das Objekt füllt und notify/notifyAll aufruft).

Wenn Sie hier die Benachrichtigung für dasselbe Objekt ausgeben (von derselben/anderen Seite des Prozesses/Codes), wird dadurch ein blockierter und wartender einzelner Thread freigegeben (nicht alle wartenden Threads – dieser freigegebene Thread wird vom JVM-Thread ausgewählt). Der Scheduler und der gesamte Sperrenerlangungsprozess für das Objekt sind die gleichen wie bei regulären.

Wenn Sie nur einen Thread haben, der dieses Objekt teilt/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 basierend auf Ihrer Geschäftslogik mehr als ein Thread Ressourcen/Objekte liest und schreibt, sollten Sie sich für notifyAll() entscheiden.

Jetzt schaue ich, wie genau der JVM den wartenden Thread identifiziert und unterbricht, wenn wir notify() für ein Objekt ausgeben ...

Obwohl es oben einige solide Antworten gibt, bin ich überrascht, wie viele Verwirrungen und Missverständnisse 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 heutzutage darin besteht, notify() in ALLEN Situationen zu VERMEIDEN, da das Problem des verlorenen Aufweckens auftritt.Wer dies nicht versteht, sollte keinen geschäftskritischen Parallelitätscode schreiben dürfen.Wenn Sie über das Herding-Problem besorgt sind, besteht eine sichere Möglichkeit, jeweils einen Thread aufzuwecken, darin, Folgendes zu tun:1.Erstellen Sie eine explizite Warteschlange für die wartenden Threads.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, das dies bereits implementiert hat.

Das Aufwachen aller Menschen spielt hier keine große Rolle.Warten Sie, benachrichtigen Sie und benachrichtigen Sie alle, alle diese werden nach dem Besitz des Objektmonitors platziert.Wenn sich ein Thread in der Wartephase befindet und notify aufgerufen wird, übernimmt dieser Thread die Sperre und kein anderer Thread kann zu diesem Zeitpunkt diese Sperre übernehmen.Ein gleichzeitiger Zugriff kann also überhaupt nicht stattfinden.Soweit ich weiß, kann ein Aufruf von „wait notify“ und „notifyall“ erst erfolgen, nachdem die Sperre für das Objekt aufgehoben wurde.Korrigieren Sie mich, wenn ich falsch liege.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top