Java: notify() vs. notifyAll()

Jeśli ktoś Wygoogluje "różnica między notify() i notifyAll()", wtedy pojawi się wiele wyjaśnień (pomijając akapity javadoc). Wszystko sprowadza się do liczby oczekujących wątków, które się budzą: jeden w notify() i wszystkie w notifyAll().

Jednak (jeśli dobrze rozumiem różnicę między tymi metodami), tylko jeden wątek jest zawsze wybierany do dalszego pozyskiwania monitora; w pierwszym przypadku wybrany przez maszynę wirtualną, w drugim przypadku wybrany przez wątek systemowy scheduler. Dokładne procedury wyboru dla obu z nich (w ogólnym przypadku) nie są znane programiście.

Jaka jest użyteczna różnica między notify () a notifyAll () ? Coś przeoczyłem?

Author: Wayne Burkett, 2008-08-31

26 answers

Mówiąc najprościej, zależy to od tego, dlaczego twoje wątki czekają na powiadomienie. Chcesz powiedzieć jednemu z oczekujących wątków, że coś się stało, czy chcesz powiedzieć wszystkim w tym samym czasie?

W niektórych przypadkach wszystkie oczekujące wątki mogą wykonać użyteczną akcję po zakończeniu oczekiwania. Przykładem może być zestaw wątków oczekujących na zakończenie określonego zadania; po zakończeniu zadania wszystkie oczekujące wątki mogą kontynuować swoją działalność. W takim przypadku użyłbyś notifyAll () Aby obudzić wszystkie oczekujące wątki w tym samym czasie.

Inny przypadek, np. wzajemnie wykluczające się blokowanie, tylko jeden z oczekujących wątków może zrobić coś pożytecznego po powiadomieniu (w tym przypadku nabyć blokadę). W takim przypadku wolisz użyć notify () . Poprawnie zaimplementowane, możesz użyć notifyAll() również w tej sytuacji, ale niepotrzebnie budzisz wątki, które i tak nic nie potrafią.

 231
Author: Liedman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-31 19:25:22

Oczywiście, notify budzi (dowolny) jeden wątek w zestawie oczekiwania, notifyAll budzi wszystkie wątki w zestawie oczekiwania. Poniższa dyskusja powinna rozwiać wszelkie wątpliwości. notifyAll powinny być używane przez większość czasu. Jeśli nie masz pewności, którego użyć, użyj notifyAll.Proszę zapoznać się z poniższym wyjaśnieniem.

Czytaj bardzo uważnie i zrozum. Proszę wysłać mi e-mail, jeśli masz jakieś pytania.

Spójrz na Producent / konsument (założenie jest klasą ProducerConsumer z dwiema metodami). JEST ZŁAMANA. (ponieważ używa notify) - tak może działać - nawet przez większość czasu, ale może również powodować impas-zobaczymy dlaczego: {]}

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;
}

Po pierwsze,

Dlaczego potrzebujemy pętli while otaczającej oczekiwanie?

Potrzebujemy while pętli w przypadku, gdy mamy taką sytuację:

Consumer 1 (C1) wprowadź zsynchronizowany blok, a bufor jest pusty, więc C1 jest umieszczany w zestawie oczekiwania (poprzez wywołanie wait). Konsument 2 (C2) ma zamiar wprowadzić metodę zsynchronizowaną (w punkcie y powyżej), ale producent P1 umieszcza obiekt w buforze, a następnie wywołuje notify. Jedynym oczekującym wątkiem jest C1, więc zostaje obudzony i próbuje ponownie zdobyć blokadę obiektu w punkcie X (powyżej).

Teraz C1 i C2 próbują zdobyć blokadę synchronizacji. Jeden z nich (nieeterministycznie) jest wybrany I wchodzi do metody, drugi jest blokowany(nie czeka - ale blokowany, próbując uzyskać blokadę metody). Powiedzmy, że C2 dostanie zamek pierwszy. C1 nadal blokuje (próbuje zdobądź zamek W x). C2 kończy metodę i zwalnia blokadę. C1 przejmuje zamek. Zgadnij co, na szczęście mamy pętlę while, ponieważ, C1 wykonuje loop check (guard) i nie jest w stanie usunąć nieistniejącego elementu z bufora (C2 już go dostał!). Gdybyśmy nie mieli while, otrzymalibyśmy IndexArrayOutOfBoundsException, gdy C1 próbuje usunąć pierwszy element z bufora!

Teraz,

Po co nam powiadomienie?

W producenta / konsumenta przykład powyżej wygląda na to, że możemy uciec z notify. Wydaje się, że tak, ponieważ możemy udowodnić, że strażnicy na pętli wait dla producenta i konsumenta wzajemnie się wykluczają. Oznacza to, że wygląda na to, że nie możemy mieć wątku czekającego zarówno w metodzie put, jak i w metodzie get, ponieważ, aby to było prawdziwe, musiałyby być prawdziwe:

buf.size() == 0 AND buf.size() == MAX_SIZE (Załóżmy, że MAX_SIZE nie jest 0)

Jednak nie jest to wystarczająco dobre, musimy użyć notifyAll. Zobaczmy. dlaczego ...

Załóżmy, że mamy bufor o rozmiarze 1 (aby ułatwić przykład). Poniższe kroki prowadzą nas do impasu. Zauważ, że za każdym razem, gdy wątek zostanie obudzony za pomocą notify, może być on niedeterministycznie wybrany przez JVM - czyli każdy oczekujący wątek może zostać obudzony. Należy również zauważyć, że gdy wiele wątków blokuje wejście do metody (tj. próbuje uzyskać blokadę), kolejność przejęcia może być niedeterministyczna. Pamiętaj również, że wątek może być tylko w jednej z metod w any one time - metody zsynchronizowane pozwalają na wykonywanie tylko jednego wątku (tzn. trzymanie blokady) dowolnych (zsynchronizowanych) metod w klasie. Jeśli zachodzi następująca sekwencja zdarzeń-wynik impasu:

Krok 1:
- P1 umieszcza 1 znak w buforze

Krok 2:
- P2 próbuje put - sprawdza pętlę oczekiwania-już znak-czeka

Krok 3:
- P3 próbuje put - sprawdza wait loop - już znak - waits

KROK 4:
- C1 próbuje uzyskać 1 znak
- C2 próbuje uzyskać 1 char - bloki przy wejściu do metody get
- C3 próbuje uzyskać 1 char - bloki przy wejściu do metody get

Krok 5:
- C1 wykonuje metodę get - pobiera znak, wywołuje notify, kończy metodę
- notify budzi P2
- Ale, C2 wchodzi metodę przed P2 może (P2 musi ponownie zażądać blokady), więc P2 blokuje przy wejściu do metody put
- C2 sprawdza pętlę oczekiwania, nie ma więcej znaków w buforze, więc waits
- C3 wprowadza metodę za C2, ale przed P2 sprawdza pętlę oczekiwania, nie ma więcej znaków w buforze, więc waits

Krok 6:
- Teraz: P3, C2 i C3 czekają!
- W końcu P2 przejmuje blokadę, umieszcza znak w buforze, wywołuje notify, kończy metodę

Krok 7:
- Powiadomienie P2 budzi P3 (pamiętaj, że każdy wątek może zostać obudzony)
- P3 sprawdza stan pętli oczekiwania, jest już znak w bufor, więc czeka.
- Koniec wątków do wywołania i trzy wątki trwale zawieszone!

Rozwiązanie: Zastąp notify znakiem notifyAll w kodzie producenta/konsumenta (powyżej).

 293
Author: xagyg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-17 03:45:50

Przydatne różnice:

  • Użyj notify () jeśli wszystkie oczekujące wątki są wymienne (kolejność ich budzenia nie ma znaczenia), lub jeśli masz tylko jeden oczekujący wątek. Częstym przykładem jest pula wątków używana do wykonywania zadań z kolejki-po dodaniu zadania jeden z wątków jest powiadamiany o wybudzeniu, wykonaniu następnego zadania i powrocie do trybu uśpienia.

  • Użyj notifyAll () w innych przypadkach, gdy oczekujące wątki mogą mieć różne cele i powinien być w stanie działać jednocześnie. Przykładem może być operacja konserwacji współdzielonego zasobu, w której wiele wątków czeka na zakończenie operacji przed uzyskaniem dostępu do zasobu.

 36
Author: David Crow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-31 19:41:28

Myślę, że to zależy od tego, jak zasoby są produkowane i zużywane. Jeśli 5 obiektów roboczych jest dostępnych naraz i masz 5 obiektów konsumenckich, sensowne byłoby OBUDZENIE wszystkich wątków za pomocą notifyAll (), aby każdy z nich mógł przetworzyć 1 obiekt roboczy.

Jeśli masz tylko jeden obiekt roboczy, jaki jest sens budzić wszystkie przedmioty konsumenckie, aby ścigać się o ten jeden obiekt? Pierwszy sprawdza dostępne prace dostanie go i wszystkie inne wątki sprawdzi i stwierdzi, że nie mają nic do zrób.

Znalazłem świetne Wyjaśnienie tutaj . W skrócie:

Metoda notify () jest powszechnie stosowana dla puli zasobów , gdzie czy dowolna liczba " konsumentów" lub "pracowników", którzy zabierają zasoby, ale gdy zasób jest dodawany do puli, tylko jeden z oczekujących konsumentów lub pracownicy mogą sobie z tym poradzić. Na metoda notifyAll () jest faktycznie stosowana w większość innych przypadków. Ściśle, jest obowiązany powiadomić kelnerów o warunek, który mógłby Zezwalaj na wielokrotne kelnerzy do kontynuowania. Ale to jest często trudno wiedzieć. Jako generał reguła, jeśli nie masz konkretnego logika użycia notify (), wtedy należy prawdopodobnie użyć notifyAll () , ponieważ często trudno jest poznać dokładnie jakie wątki będą czekać na konkretny przedmiot i dlaczego.

 18
Author: andyuk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-31 19:25:10

Zauważ, że w przypadku narzędzi współbieżnych masz również wybór pomiędzy signal() i signalAll(), ponieważ te metody są tam wywoływane. Więc pytanie pozostaje ważne nawet z java.util.concurrent.

Doug Lea przywołuje interesujący punkt w swojej słynnej książce : Jeśli notify() i Thread.interrupt() zdarzy się w tym samym czasie, notify może się zgubić. Jeśli to może się zdarzyć i ma dramatyczne implikacje notifyAll() jest bezpieczniejszym wyborem, mimo że płacisz cenę nad głową (budząc zbyt wiele wątków większość czas).

 11
Author: Gray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-07 17:05:34

Od Joshuy Blocha, samego Guru Javy w Effective Java 2nd edition:

"pozycja 69: preferuj narzędzia współbieżne, aby czekały i powiadamiały".

 10
Author: Steve McLeod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-03 14:26:12

Oto przykład. Uruchom go. Następnie zmień jedną z notifyAll() na notify () i zobacz, co się stanie.

ProducerConsumerExample class

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

Klasa Dropbox

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

Klasa konsumencka

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) { }
        }
    }
}

Klasa producenta

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) { }
        }
    }
}
 9
Author: Erik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-05 16:11:25

Krótkie podsumowanie:

Zawsze preferuj notifyAll () nad notify () , chyba że masz masowo równoległą aplikację, w której duża liczba wątków robi to samo.

Explanation:

Notify () [.../ align = "left" / nić. Ponieważ notify () nie pozwala określić wątku, który jest obudzony, jest przydatny tylko w aplikacjach masowo równoległych - że jest, Programy z dużą liczbą wątki, wszyscy wykonują podobne obowiązki. W takiej aplikacji nie obchodzi cię, który wątek zostanie obudzony.

Źródło: https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

Porównaj notify () z notifyAll () w opisanej powyżej sytuacji: aplikacja masowo równoległa, w której wątki robią to samo. Jeśli wywołasz notifyAll () w takim przypadku, notifyAll () wywoła przebudzenie (tj. scheduling) ogromnej liczby wątków, wiele z nich niepotrzebnie (ponieważ tylko jeden wątek może faktycznie działać, a mianowicie wątek, który otrzyma monitor dla obiektu wait(), notify(), lub notifyAll() został wywołany), dlatego marnuje zasoby obliczeniowe.

Tak więc, jeśli nie masz aplikacji, w której ogromna liczba wątków robi to samo jednocześnie, preferuj notifyAll() zamiast notify(). Dlaczego? Ponieważ, jak inni użytkownicy mają już odpowiedział na tym forum, powiadom()

Budzi pojedynczy wątek, który czeka na monitorze tego obiektu. [...] The wybór jest arbitralny i odbywa się według uznania wdrożenie.

Źródło: Java SE8 API ( https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify--)

Wyobraź sobie, że masz aplikację producer consumer, w której konsumenci są gotowi (tj. wait () ing) do konsumpcji, producenci są gotowe (tzn. wait () ing) do wyprodukowania, a kolejka elementów (do wyprodukowania / konsumpcji) jest pusta. W takim przypadku notify() może obudzić tylko konsumentów, a nigdy producentów, ponieważ wybór, kto się obudzi, jest arbitralny . Cykl Produkcyjno-konsumencki nie poczyniłby żadnych postępów, chociaż producenci i konsumenci są gotowi odpowiednio produkować i konsumować. Zamiast tego konsument zostaje obudzony (tzn. zostawia status wait () ), nie wyjmuje przedmiotu z Kolejka, ponieważ jest pusta, a notify () jest kolejnym konsumentem, który kontynuuje.

Natomiast notifyAll () obudza zarówno producentów, jak i konsumentów. Wybór, kto jest zaplanowany, zależy od harmonogramu. Oczywiście, w zależności od implementacji schedulera, scheduler może również zaplanować tylko konsumentów (np. jeśli przypisujesz wątkom konsumenckim bardzo wysoki priorytet). Jednak założenie jest tutaj, że niebezpieczeństwo Scheduler Scheduler tylko konsumentów jest mniejsze niż niebezpieczeństwo JVM budzi tylko konsumentów, ponieważ każdy rozsądnie zaimplementowany scheduler nie podejmuje tylko arbitralnych decyzji. Raczej większość implementacji schedulera podejmuje przynajmniej pewien wysiłek, aby zapobiec głodowi.

 8
Author: ansd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-25 13:28:48

Jestem bardzo zaskoczony, że nikt nie wspomniał o niesławnym" lost wakeup " problem (google it).

W Zasadzie:

  1. Jeśli masz kilka wątków oczekujących na ten sam warunek i,
  2. wiele wątków, które mogą spowodować przejście ze stanu a do stanu B i,
  3. wiele wątków, które mogą spowodować przejście ze stanu B do stanu A (zwykle te same wątki, co w 1.) oraz,
  4. przejście ze stanu a do B powinno powiadamiać wątki w 1.

THEN powinieneś używać notifyAll, chyba że masz udowodnione gwarancje, że utracone przebudzenia są niemożliwe.

Typowym przykładem jest równoległa Kolejka FIFO, gdzie: wielokrotny rekordzista (1. i 3. powyżej) może zmienić kolejkę z pustej na niepustą wielokrotny wicemistrz (2. powyżej) można czekać na warunek " kolejka nie jest pusta" empty- > non-empty should notify dequeuers

Możesz w prosty sposób napisać przeplot operacji, w których zaczynając od pustej kolejki, 2 zapytań i 2 dequeuer wchodzi w interakcję, a 1 enqueuer pozostaje śpiący.

Jest to problem prawdopodobnie porównywalny z problemem impasu.

 5
Author: NickV,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-15 16:50:48

Mam nadzieję, że rozwiąże to pewne wątpliwości.

Powiadom() : metoda notify () budzi jeden wątek oczekujący za blokadę (pierwszy wątek, który wywołał wait() na tej blokadzie).

NotifyAll() : metoda notifyAll() budzi wszystkie wątki oczekujące na blokadę; JVM wybiera jeden z wątków z listy wątków oczekujących na blokadę i budzi ten wątek w górę.

W przypadku pojedynczego wątku czekającego na blokadę, nie ma znacząca różnica między notify () i notifyAll (). Jednakże, gdy na blokadę czeka więcej niż jeden wątek, zarówno notify (), jak i notifyAll (), dokładny wątek obudzony jest pod kontrolą JVM i nie można programowo kontrolować budzenia określonego wątku.

Na pierwszy rzut oka wydaje się, że dobrym pomysłem jest wywołanie notify (), aby obudzić jeden wątek; może wydawać się niepotrzebne obudzić wszystkie wątki. Jednak problem z powiadom() jest to, że wątek obudzony może nie być odpowiedni do obudzenia (wątek może czekać na inny warunek, lub warunek nie jest nadal spełniony dla tego wątku itp.). w takim przypadku , notify () może zostać utracone i żaden inny wątek nie obudzi się potencjalnie prowadząc do pewnego rodzaju impasu(powiadomienie jest utracone, a wszystkie inne wątki czekają na powiadomienie-na zawsze).

Aby uniknąć tego problemu , zawsze lepiej jest wywołać notifyAll (), gdy na blokadę czeka więcej niż jeden wątek (lub więcej niż jeden warunek, na którym czeka się). Metoda notifyAll () budzi wszystkie wątki, więc nie jest zbyt wydajna. jednak ta utrata wydajności jest znikoma w rzeczywistych zastosowaniach.

 5
Author: pardeep131085,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-29 06:26:51

Wszystkie powyższe odpowiedzi są poprawne, o ile mogę powiedzieć, więc powiem ci coś innego. Dla kodu produkcyjnego naprawdę powinieneś używać klas w Javie.util./ align = "left" / W obszarze współbieżności w Javie niewiele mogą Dla ciebie zrobić.

 4
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-31 19:50:58

Ta odpowiedź jest graficznym przepisaniem i uproszczeniem doskonałej odpowiedzi xagyg , w tym komentarzy eran.

Po co stosować notifyAll, nawet jeśli każdy produkt jest przeznaczony dla jednego konsumenta?

Rozważmy producentów i konsumentów, uproszczone w następujący sposób.

Producent:

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

Konsument:

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

Załóżmy, że 2 producentów i 2 konsumentów, dzieląc bufor wielkości 1. Poniższy rysunek przedstawia scenariusz prowadzący do impas , którego można by uniknąć, gdyby wszystkie użyte wątki zgłosiły się.

Każda wiadomość jest oznaczona znakiem budzącego się wątku.

impas z powodu powiadomienia

 4
Author: Marco,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-03 10:16:03

notify() obudzi jeden wątek, podczas gdy notifyAll() obudzi wszystkich. Z tego, co wiem, nie ma kompromisu. Ale jeśli nie jesteś pewien, co notify() zrobi z Twoimi wątkami, użyj notifyAll(). Działa jak urok za każdym razem.

 3
Author: Spoike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-31 19:19:21

notify() pozwala pisać bardziej efektywny Kod niż notifyAll().

Rozważ następujący fragment kodu, który jest wykonywany z wielu równoległych wątków:

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

Może być bardziej wydajny za pomocą notify():

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

W przypadku, gdy masz dużą liczbę wątków, lub jeśli warunek pętli oczekiwania jest kosztowny do oceny, notify() będzie znacznie szybszy niż notifyAll(). Na przykład, jeśli masz 1000 wątków, to 999 wątków zostanie obudzonych i ocenionych po najpierw notifyAll(), potem 998, potem 997 i tak dalej. Przeciwnie, z rozwiązaniem notify(), tylko jeden wątek zostanie obudzony.

Użyj notifyAll(), gdy musisz wybrać, który wątek wykona następną pracę:

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

Wreszcie, ważne jest, aby zrozumieć, że w przypadku notifyAll(), kod wewnątrz synchronized bloków, które zostały obudzone, będzie wykonywany kolejno, nie wszystkie naraz. Załóżmy, że w powyższym przykładzie czekają trzy wątki, a czwarty wątek wywołuje notifyAll(). Wszystkie trzy wątki zostaną obudzone, ale tylko jeden rozpocznie wykonywanie i sprawdzi stan pętli while. Jeśli warunek to true, wywoła on ponownie wait() i dopiero wtedy drugi wątek rozpocznie wykonywanie i sprawdzi swój warunek pętli while itd.

 3
Author: Alexander Ryzhov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-17 23:23:24

Oto prostsze Wyjaśnienie:

Masz rację, że niezależnie od tego, czy używasz notify () czy notifyAll (), natychmiastowym rezultatem jest to, że dokładnie jeden inny wątek przejmie monitor i rozpocznie wykonywanie. (Zakładając, że niektóre wątki zostały zablokowane w wait () dla tego obiektu, inne niepowiązane wątki nie pochłaniają wszystkich dostępnych rdzeni itp.) Uderzenie następuje później.

Przypuśćmy, że wątki A, B I C czekały na ten obiekt, a wątek A otrzyma monitor. Różnica polega na tym, co zdarza się, gdy A uwalnia monitor. Jeśli użyłeś notify (), to B I C są nadal zablokowane w wait (): nie czekają na monitorze, czekają na powiadomienie. Kiedy a zwolni monitor, B I C nadal będą tam siedzieć i czekać na notify ().

Jeśli użyłeś notifyAll (), to B I C przeszły stan "wait for notification" i oba oczekują na przejęcie monitora. Gdy a zwolni monitor, B lub C GO przejmie (zakładając, że nie ma innych wątków konkurują o ten monitor) i rozpocząć wykonywanie.

 3
Author: Steve,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-25 00:18:48

zaczerpnięte z bloga o efektywnej Javie:

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

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

Rozumiem więc (ze wspomnianego bloga, komentarz by "Yann TM" on accepted answer and Java docs):

  • notify (): JVM budzi jeden z oczekujących wątków na tym obiekcie. Wybór wątku jest dokonywany arbitralnie bez uczciwości. Więc ta sama nić może być obudzona ponownie i ponownie. Tak więc stan systemu zmienia się, ale nie dokonuje się żadnego rzeczywistego postępu. W ten sposób tworząc livelock .
  • notifyAll (): JVM budzi wszystkie wątki i wtedy wszystkie wątki ścigają się o blokadę tego obiektu. Teraz CPU scheduler wybiera wątek, który przejmuje blokadę tego obiektu. Ten proces selekcji byłby znacznie lepszy niż wybór przez JVM. W ten sposób zapewnienie życia.
 3
Author: rajya vardhan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-25 21:47:45

Spójrz na kod wysłany przez @xagyg.

Załóżmy, że dwa różne wątki czekają na dwa różne warunki:
pierwszy wątek czeka na buf.size() != MAX_SIZE, a drugi wątek czeka na buf.size() != 0.

Przypuśćmy, że w pewnym momencie buf.size() nie jest równa 0 . JVM wywołuje notify() zamiast notifyAll(), a pierwszy wątek jest powiadamiany (nie drugi).

Pierwszy wątek jest wybudzony, sprawdza buf.size() który może zwrócić MAX_SIZE, oraz wraca do czekania. Drugi wątek nie budzi się, nadal czeka i nie wywołuje get().

 2
Author: alxlevin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-28 13:24:29

notify() budzi pierwszy wątek, który wywołał wait() na tym samym obiekcie.

notifyAll() budzi wszystkie wątki, które wywołały wait() na tym samym obiekcie.

Wątek o najwyższym priorytecie zostanie uruchomiony jako pierwszy.

 1
Author: Kshitij Banerjee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-05 19:58:17

Chciałbym wspomnieć o tym, co w praktyce tłumaczy się współbieżnością w Javie:

Pierwszy punkt, czy powiadamiać czy powiadamiać?

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

Jeśli dwa wątki A i B czekają na różne predykaty warunku o tym samym stanie kolejki i powiadomień jest wywoływany, to jest do JVM do który wątek JVM powiadomi.

Teraz jeśli notify było przeznaczone dla wątku a i JVM notified thread B, to wątek B obudzi się i zobaczy, że to powiadomienie nie jest przydatne więc to znowu poczeka. I Thread A nigdy się o tym nie dowie przegapiłem sygnał i ktoś porwał powiadomienie.

Więc wywołanie notifyAll rozwiąże ten problem, ale znowu będzie wpływ na wydajność, ponieważ powiadomi wszystkie wątki i wszystkie wątki będą konkurować o tę samą blokadę i będzie to wiązało się z przełączaniem kontekstu, a co za tym idzie obciążenie procesora. Ale powinniśmy dbać o wydajność tylko wtedy, gdy jest zachowywania się poprawnie, jeśli samo zachowanie nie jest poprawne to wydajność jest bezużyteczna.

Problem ten można rozwiązać za pomocą obiektu warunkowego jawnego blokowania Lock, dostarczonego w jdk 5, ponieważ zapewnia on różne oczekiwanie dla każdego predykatu warunku. Tutaj będzie się zachowywał poprawnie i nie będzie problemu z wydajnością, ponieważ wywoła sygnał i upewni się, że tylko jeden wątek czeka na ten warunek

 1
Author: AKS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-06 05:34:22

Notify powiadomi tylko jeden wątek, który jest w stanie oczekiwania, podczas gdy notify all powiadomi wszystkie wątki w stanie oczekiwania teraz wszystkie zgłoszone wątki i wszystkie zablokowane wątki kwalifikują się do blokady, z której tylko jeden otrzyma blokadę, a wszystkie inne (w tym te, które są w stanie oczekiwania wcześniej) będą w stanie zablokowanym.

 1
Author: Saurabh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-22 18:25:48

notify() - wybiera losowy wątek z zestawu wait obiektu i umieszcza go w stanie BLOCKED. Pozostałe wątki w zestawie wait obiektu są nadal w stanie WAITING.

notifyAll() - przenosi wszystkie wątki z zestawu wait obiektu do stanu BLOCKED. Po użyciu notifyAll(), nie ma wątków pozostających w zestawie oczekiwania udostępnionego obiektu, ponieważ wszystkie z nich są teraz w stanie BLOCKED, a nie w stanie WAITING.

BLOCKED - Zablokowany do przejęcia blokady. WAITING - oczekiwanie na powiadomienie (lub zablokowane dla zakończenia połączenia).

 1
Author: fireboy91,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-25 13:21:11

Istnieją trzy stany dla wątku.

  1. WAIT-wątek nie używa żadnego cyklu procesora
  2. BLOCKED-wątek jest zablokowany próbując zdobyć monitor.It może nadal używać cykli procesora
  3. RUNNING-wątek jest uruchomiony.

Teraz, gdy wywoływana jest funkcja notify (), JVM wybiera jeden wątek i przenosi go do stanu zablokowanego, a tym samym do stanu uruchomionego, ponieważ nie ma konkurencji dla obiektu monitor.

Po wywołaniu notifyAll (), JVM wybiera wszystkie wątki i przenieść wszystkie z nich do stanu zablokowanego. Wszystkie te wątki otrzymają blokadę obiektu na zasadzie priorytetu.Wątek, który jest w stanie zdobyć monitor jako pierwszy, będzie mógł przejść do stanu uruchomionego i tak dalej.

 1
Author: S_R,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-08 07:46:58

Aby podsumować doskonałe szczegółowe wyjaśnienia powyżej, i w najprostszy sposób mogę myśleć, jest to spowodowane ograniczeniami wbudowanego monitora JVM, który 1) jest nabywany na całej jednostce synchronizacji (blok lub obiekt) i 2) nie dyskryminuje konkretnego warunku oczekiwanego/powiadamianego/o/.

Oznacza to, że jeśli wiele wątków czeka na różne warunki i zostanie użyta funkcja notify (), wybrany wątek może nie być tym, który zrobiłby postępy na nowo speĹ 'nionym warunku-powodujÄ ... c Ăłw niÄ ... (oraz inne aktualnie jeszcze oczekujÄ ... ce niÄ ... ce ktĂłre byĹ' yby w stanie speĹ ' niÄ ‡ ten warunek, itp..), aby nie móc robić postępów, a w końcu głodować lub zawieszać program.

W przeciwieństwie do notifyAll () umożliwia wszystkim oczekującym wątkom ponowne uzyskanie blokady i sprawdzenie ich odpowiedniego stanu, umożliwiając tym samym osiągnięcie postępu.

Więc notify () można bezpiecznie używać tylko wtedy, gdy jakikolwiek wątek oczekujący jest gwarantowane, aby umożliwić postęp, jeśli zostanie wybrany, co jest ogólnie spełnione, gdy wszystkie wątki w tym samym monitorze sprawdzają tylko jeden i ten sam warunek - dość rzadki przypadek w rzeczywistych aplikacjach.

 0
Author: KingCode,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-11 15:46:20

Kiedy wywołasz wait () "obiektu" (oczekując, że zostanie nabyta blokada obiektu), intern zwolni blokadę tego obiektu, a inne wątki help będą miały blokadę tego "obiektu", w tym scenariuszu będzie więcej niż 1 Wątek czekający na "zasób / obiekt"(biorąc pod uwagę, że inne wątki również wydały oczekiwanie na ten sam powyższy obiekt i w dół będzie wątek, który wypełni zasób / obiekt i wywoła notifyall).

Tutaj, gdy wydasz notify of the same object (from the same/other side of the process / code), this will release a blocked and waiting single thread (not all the waiting threads -- this released thread will be picked by JVM Thread Scheduler and all the lock uzyskiwanie process on the object is same as regular).

Jeśli masz tylko jeden wątek , który będzie współdzielił/pracował nad tym obiektem, Można używać samej metody notify() w implementacji wait-notify.

Jeśli jesteś w sytuacji, w której więcej zamiast jednego wątku czytać i pisać na zasobach / obiektach w oparciu o Twoją logikę biznesową, powinieneś przejść do notifyAll ()

Teraz patrzę, jak dokładnie jvm identyfikuje i łamie oczekujący wątek, gdy wydajemy notify () na obiekcie ...

 0
Author: Deepak sai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-21 18:32:01

Chociaż są pewne solidne odpowiedzi powyżej, jestem zaskoczony liczbą nieporozumień i nieporozumień, które przeczytałem. To prawdopodobnie dowodzi, że należy używać Javy.util.współbieżne w miarę możliwości zamiast próbować pisać własny zepsuty kod współbieżny. Wracając do pytania: podsumowując, najlepszą praktyką jest unikanie notify () we wszystkich sytuacjach z powodu problemu utraconego przebudzenia. Każdy, kto tego nie rozumie, nie powinien mieć prawa do pisania mission critical concurrency kod. Jeśli martwisz się o problem pasterstwa, jednym z bezpiecznych sposobów osiągnięcia obudzenia jednego wątku na raz jest: 1. Tworzenie jawnej kolejki oczekujących wątków; 2. Niech każdy z wątków w kolejce czeka na swojego poprzednika; 3. Niech każde wywołanie wątku notifyAll () zostanie zakończone. Możesz też użyć Javy.util./ align = "left" / * , które już to wdrożyły.

 0
Author: jzl106,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-28 14:03:05

Obudzenie się nie ma tu większego znaczenia. wait notify i notifyall, wszystkie te są umieszczane po posiadaniu monitora obiektu. Jeśli wątek znajduje się w fazie oczekiwania i zostanie wywołane powiadomienie, wątek ten zajmie blokadę i żaden inny wątek w tym momencie nie może zająć tej blokady. Tak więc współbieżny dostęp nie może mieć miejsca w ogóle. Z tego co wiem każde wezwanie do czekania i powiadamiania może być wykonane dopiero po zrobieniu blokady na obiekcie. Popraw mnie, jeśli się mylę.

 -2
Author: Abhay Bansal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-14 08:17:21