Jeśli jeden Googles dla „różnicy między notify()
i notifyAll()
”, pojawi się wiele wyjaśnień (pomijając akapity javadoc). To wszystko sprowadza się do liczby wątków oczekujących jest obudzony: jeden w notify()
i wszystkie in notifyAll()
.
Jednak (jeśli dobrze rozumiem różnicę między tymi metodami), tylko jeden wątek jest zawsze wybierany do dalszego monitorowania monitora; w pierwszym przypadku ten wybrany przez maszynę wirtualną, w drugim przypadku ten wybrany przez systemowy program do planowania wątków. Dokładne procedury wyboru dla obu z nich (w ogólnym przypadku) nie są znane programiście.
Jaka jest zatem użyteczna różnica między powiadomieniem () a powiadomieniemAll () ? Czy coś brakuje?
java
multithreading
Siergiej Mikhanov
źródło
źródło
synchronized
, dobrego , z wyjątkiem niektórych , raczej rzadkie przypadki użycia.notifyAll()
przypadku: pozostałe wątki po pierwszym pozostają w stanie czuwania i przejmują monitor, jeden po drugim. W tymnotify
przypadku żaden z pozostałych wątków nie został nawet obudzony. Więc funkcjonalnie są bardzo różne!blocked
a nie.waiting
Gdyblocked
jego exec zostanie tymczasowo zawieszone, aż inny wątek znajdzie się wsync
bloku.Odpowiedzi:
To nie jest poprawne.
o.notifyAll()
budzi wszystkie wątki zablokowane wo.wait()
połączeniach. Nitki są dozwolone tylko do powrotu zo.wait()
jeden po drugim, ale każdy z nich będzie dostać swoją kolej.Mówiąc najprościej, zależy to od tego, dlaczego twoje wątki czekają na powiadomienie. Czy chcesz powiedzieć jednemu z oczekujących wątków, że coś się wydarzyło, czy też chcesz powiedzieć wszystkim jednocześnie?
W niektórych przypadkach wszystkie wątki oczekujące mogą podjąć przydatne działania 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ś funkcji replaceAll (), aby obudzić wszystkie oczekujące wątki w tym samym czasie.
W innym przypadku, na przykład blokując się wzajemnie, tylko jeden z oczekujących wątków może zrobić coś użytecznego po otrzymaniu powiadomienia (w tym przypadku uzyskać blokadę). W takim przypadku wolisz użyć funkcji powiadomień () . Prawidłowo wdrożone, to mógłby użyć notifyAll () w tej sytuacji, jak również, ale byś niepotrzebnie budzić wątki, które nie mogą nic zrobić tak.
W wielu przypadkach kod oczekiwania na warunek zostanie zapisany jako pętla:
W ten sposób, jeśli
o.notifyAll()
połączenie budzi więcej niż jeden wątek oczekujący, a pierwszy, który powróci zo.wait()
make, pozostawia warunek w stanie fałszywym, wówczas pozostałe wątki, które zostały przebudzone, wrócą do oczekiwania.źródło
Oczywiście,
notify
budzi (dowolny) jeden wątek w zestawie oczekiwania,notifyAll
budzi wszystkie wątki w zestawie oczekiwania. Poniższa dyskusja powinna wyjaśnić wszelkie wątpliwości.notifyAll
powinien być używany przez większość czasu. Jeśli nie masz pewności, którego użyć, skorzystaj z.notifyAll
Przeczytaj wyjaśnienie poniżej.Przeczytaj bardzo uważnie i zrozum. Proszę wysłać mi e-mail, jeśli masz jakieś pytania.
Spójrz na producenta / konsumenta (założenie jest klasą ProducerConsumer z dwiema metodami). JEST ZŁAMANY (ponieważ używa
notify
) - tak, MOŻE działać - nawet przez większość czasu, ale może również powodować impas - zobaczymy, dlaczego:PO PIERWSZE,
Dlaczego potrzebujemy pętli while otaczającej oczekiwanie?
Potrzebujemy
while
pętli na wypadek wystąpienia takiej sytuacji:Konsument 1 (C1) wchodzi do zsynchronizowanego bloku, a bufor jest pusty, więc C1 jest umieszczany w zestawie oczekiwania (za pośrednictwem
wait
połączenia). Konsument 2 (C2) wkrótce wejdzie do metody zsynchronizowanej (w punkcie Y powyżej), ale producent P1 umieszcza obiekt w buforze, a następnie wywołujenotify
. Jedynym oczekującym wątkiem jest C1, więc jest on budzony i teraz próbuje ponownie uzyskać blokadę obiektu w punkcie X (powyżej).Teraz C1 i C2 próbują uzyskać blokadę synchronizacji. Jeden z nich (niedeterministycznie) jest wybierany i wchodzi do metody, drugi jest blokowany (nie czekając - ale blokowany, próbując uzyskać blokadę metody). Powiedzmy, że C2 najpierw otrzymuje blokadę. C1 nadal blokuje (próbuje zdobyć zamek w X). C2 kończy metodę i zwalnia blokadę. Teraz C1 przejmuje blokadę. Zgadnij, na szczęście mamy
while
pętlę, ponieważ C1 wykonuje kontrolę pętli (warta) i nie można usunąć nieistniejącego elementu z bufora (C2 już go dostał!). Gdybyśmy nie mieliwhile
, otrzymalibyśmy,IndexArrayOutOfBoundsException
ponieważ C1 próbuje usunąć pierwszy element z bufora!TERAZ,
Ok, teraz dlaczego potrzebujemy powiadomić?
W powyższym przykładzie producent / konsument wygląda na to, że możemy uciec
notify
. Wydaje się, że tak jest, ponieważ możemy udowodnić, że strażnicy w pętlach oczekiwania dla producenta i konsumenta wykluczają się wzajemnie. To znaczy, wygląda na to, że nie możemy mieć wątku oczekującego zarówno wput
metodzie, jak iget
metodzie, ponieważ aby to było prawdą, musiałyby być spełnione następujące warunki:buf.size() == 0 AND buf.size() == MAX_SIZE
(zakładamy, że MAX_SIZE nie jest równy 0)JEDNAK nie jest to wystarczające, NALEŻY użyć
notifyAll
. Zobaczmy, dlaczego ...Załóżmy, że mamy bufor wielkości 1 (aby ułatwić naśladowanie przykładu). Poniższe kroki prowadzą nas do impasu. Zauważ, że W KAŻDYM momencie wątek budzi się z powiadomieniem, może on zostać niedeterministycznie wybrany przez JVM - to znaczy każdy oczekujący wątek może zostać obudzony. Zauważ również, że gdy wiele wątków blokuje wejście do metody (tj. Próbuje uzyskać blokadę), kolejność pozyskiwania może być niedeterministyczna. Pamiętaj również, że wątek może być jednocześnie w jednej z metod - metody zsynchronizowane pozwalają na wykonywanie tylko jednego wątku (tj. Blokowanie) dowolnej (zsynchronizowanej) metody w klasie. Jeśli wystąpi następująca sekwencja zdarzeń - zakleszczenie:
KROK 1:
- P1 umieszcza 1 bufor w buforze
KROK 2:
- P2 próbuje
put
- sprawdza pętlę oczekiwania - już znak - czekaKROK 3:
- P3 próbuje
put
- sprawdza pętlę oczekiwania - już znak - czekaKROK 4:
- C1 próbuje uzyskać 1 znak
- C2 próbuje uzyskać 1 znak - bloki przy wejściu do
get
metody- C3 próbuje uzyskać 1 znak - bloki przy wejściu do
get
metodyKROK 5:
- C1 jest wykonanie
get
metody - dostaje char, rozmowynotify
metoda, wyjścia- The
notify
obudzi P2- ALE, C2 wchodzi metodę przed P2 może (P2 musi odkupić zamek), więc bloków P2 na wejściu do
put
metody- C2 sprawdza pętlę oczekiwania, nie ma już znaków w buforze, więc czeka
- C3 wchodzi do metody po C2, ale przed P2, sprawdza pętlę oczekiwania, nie ma więcej znaków w buforze, więc czeka
KROK 6:
- TERAZ: czekają P3, C2 i C3!
- W końcu P2 przejmuje blokadę, umieszcza znak w buforze, wywołuje powiadomienia, wychodzi z metody
KROK 7:
- Powiadomienie P2 budzi P3 (pamiętaj, że każdy wątek może zostać obudzony)
- P3 sprawdza warunek pętli oczekiwania, w buforze jest już znak, więc czeka.
- BRAK WIĘCEJ NICI, ABY ZADZWOŃĆ DO POWIADOMIENIA, I TRZY NICI TRWAŁO ZAWIESZONE!
Rozwiązanie: Wymień
notify
sięnotifyAll
w kodzie producent / konsumentów (powyżej).źródło
notify
powoduje, że P3 (wybrany wątek w tym przykładzie) przechodzi od punktu, w którym czekał (tj. wwhile
pętli). Istnieją inne przykłady, które nie powodują zakleszczenia, jednak w tym przypadku użycienotify
nie gwarantuje kodu bez zakleszczenia. ZastosowanienotifyAll
robi.Przydatne różnice:
Użyj powiadomienia (), jeśli wszystkie oczekujące wątki są wymienne (kolejność ich budzenia nie ma znaczenia) lub jeśli masz tylko jeden wątek oczekujący. Typowym 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 przebudzeniu, wykonaniu następnego zadania i przejściu w tryb uśpienia.
Użyj powiadomieniaAll () w innych przypadkach, gdy oczekujące wątki mogą mieć różne cele i powinny być w stanie działać jednocześnie. Przykładem jest operacja konserwacji współdzielonego zasobu, w której wiele wątków czeka na zakończenie operacji przed uzyskaniem dostępu do zasobu.
źródło
Myślę, że to zależy od tego, jak zasoby są wytwarzane i konsumowane. Jeśli dostępnych jest 5 obiektów roboczych naraz, a masz 5 obiektów konsumenckich, warto obudzić wszystkie wątki za pomocą funkcji powiadomieńAll (), aby każdy mógł przetworzyć 1 obiekt roboczy.
Jeśli masz tylko jeden obiekt roboczy, to po co budzić wszystkie obiekty konsumenckie, aby ścigać się o ten jeden obiekt? Pierwszy sprawdzi, czy dostępna praca jest dostępna, a wszystkie pozostałe wątki sprawdzą i stwierdzą, że nie mają nic do roboty.
Znalazłem tutaj świetne wytłumaczenie . W skrócie:
źródło
Zauważ, że dzięki narzędziom do współbieżności masz również wybór pomiędzy,
signal()
asignalAll()
ponieważ metody te są tam nazywane. Tak więc pytanie pozostaje ważne nawet zjava.util.concurrent
.Doug Lea porusza ciekawy punkt w swojej słynnej książce : jeśli
notify()
aThread.interrupt()
zdarzy się w tym samym czasie, powiadomienie może się zgubić. Jeśli może się to zdarzyć i ma to dramatyczne implikacje,notifyAll()
jest bezpieczniejszym wyborem, nawet jeśli płacisz za koszty ogólne (budzenie zbyt wielu wątków przez większość czasu).źródło
Krótkie podsumowanie:
Zawsze preferuj powiadomienieAll () niż powiadomienie (), chyba że masz masowo równoległą aplikację, w której duża liczba wątków robi to samo.
Wyjaśnienie:
źródło: https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
Porównaj powiadomienie () z powiadomieniem () w opisanej powyżej sytuacji: aplikacja masowo równoległa, w której wątki robią to samo. Jeśli zadzwonisz notifyAll () w tym przypadku notifyAll () będzie wywoływać budzi się (tj harmonogramu) z ogromnej liczby wątków, wiele z nich niepotrzebnie (bo tylko jeden wątek może właściwie postępować, a mianowicie wątek, który zostanie przyznano monitoruje obiekt , który został wywołany wait () , powiadomienie () lub powiadomienieAll () ), marnując zasoby komputerowe.
Tak więc, jeśli nie masz aplikacji, w której ogromna liczba wątków robi to samo jednocześnie, wolisz powiadomićAll () niż powiadomić () . Dlaczego? Ponieważ, jak inni użytkownicy już odpowiedzieli na tym forum, powiadom ()
źródło: Java SE8 API ( https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify-- )
Wyobraź sobie, że masz aplikację konsumenta producenta, w której konsumenci są gotowi (tj. Czekać () ing) do konsumpcji, producenci są gotowi (tj. Czekać () ing) do produkcji, a kolejka produktów (do wyprodukowania / konsumpcji) jest pusta. W takim przypadku funkcja powiadomienie () może obudzić tylko konsumentów, a nigdy producentów, ponieważ wybór, kto się obudzi, jest arbitralny . Cykl konsumencki producenta nie zrobiłby żadnego postępu, chociaż producenci i konsumenci są gotowi odpowiednio do produkcji i konsumpcji. Zamiast tego budzi się konsument (tzn. Pozostawia status wait () ), nie usuwa elementu z kolejki, ponieważ jest pusty, i powiadamia innego konsumenta, aby kontynuować.
W przeciwieństwie do tego, replaceAll () budzi zarówno producentów, jak i konsumentów. Wybór harmonogramu zależy od harmonogramu. Oczywiście, w zależności od implementacji programu planującego, program planujący może również planować tylko konsumentów (np. Jeśli przypisujesz wątki konsumenckie o bardzo wysokim priorytecie). Założono tutaj jednak, że niebezpieczeństwo, że harmonogram szereguje tylko konsumentów, jest mniejsze niż niebezpieczeństwo, że JVM budzi tylko konsumentów, ponieważ jakikolwiek rozsądnie wdrożony harmonogram nie podejmuje tylko arbitralnych decyzji. Przeciwnie, większość implementacji harmonogramu podejmuje przynajmniej pewne wysiłki, aby zapobiec głodowi.
źródło
Oto przykład. Uruchom. Następnie zmień jedną z funkcji powiadomieńAll () na powiadomienie () i zobacz, co się stanie.
ProducerConsumerExample klasa
Klasa Dropbox
Klasa konsumencka
Klasa producenta
źródło
Joshua Bloch, sam Java Guru w Effective Java 2nd edition:
„Pozycja 69: Preferuj narzędzia współbieżności, aby czekać i powiadamiać”.
źródło
Mam nadzieję, że rozwiąże to pewne wątpliwości.
replace () : Metoda powiadomienie () budzi jeden wątek oczekujący na blokadę (pierwszy wątek, który wywołał wait () na tej blokadzie).
replaceAll () : Metoda replaceAll () 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 przypadku pojedynczego wątku oczekującego na blokadę, nie ma znaczącej różnicy między powiadomieniem () a powiadomieniemAll (). Jednakże, gdy na blokadę czeka więcej niż jeden wątek, zarówno w powiadomieniu (), jak i powiadomieniuAll (), dokładny przebudzony wątek znajduje się pod kontrolą JVM i nie można programowo kontrolować przebudzenia określonego wątku.
Na pierwszy rzut oka wydaje się, że dobrym pomysłem jest wywołanie funkcji powiadomień () w celu wybudzenia jednego wątku; obudzenie wszystkich wątków może wydawać się niepotrzebne. Jednak problem z powiadomieniem () polega na tym, że wątek obudzony może nie być odpowiedni do przebudzenia (wątek może czekać na inny warunek lub warunek nadal nie jest spełniony dla tego wątku itp.). W takim przypadku powiadomienie () może zostać utracone i żaden inny wątek nie obudzi się potencjalnie prowadząc do pewnego rodzaju zakleszczenia (powiadomienie zostanie utracone, a wszystkie inne wątki będą czekać na powiadomienie - na zawsze).
Aby uniknąć tego problemu , zawsze lepiej jest wywołać powiadomienieAll (), gdy na blokadę czeka więcej niż jeden wątek (lub więcej niż jeden warunek, w którym oczekiwanie jest wykonywane). Metoda replaceAll () budzi wszystkie wątki, więc nie jest bardzo wydajna. ta utrata wydajności jest jednak nieistotna w rzeczywistych zastosowaniach.
źródło
Istnieją trzy stany dla wątku.
Teraz, gdy wywoływane jest powiadomienie (), JVM wybiera jeden wątek i przenosi go do stanu ZABLOKOWANEGO, a zatem do stanu URUCHOMIENIE, ponieważ obiekt monitorujący nie konkuruje.
Po wywołaniu funkcji powiadomieńAll () JVM wybiera wszystkie wątki i przenosi je do stanu ZABLOKOWANEGO. Wszystkie te wątki otrzymają blokadę obiektu na zasadzie pierwszeństwa. Wątek, który może najpierw przejąć monitor, będzie mógł najpierw przejść do stanu RUNNING i tak dalej.
źródło
Jestem bardzo zaskoczony, że nikt nie wspominał o niesławnym problemie „zagubionego budzenia” (google it).
Gruntownie:
Następnie powinieneś skorzystać z funkcji powiadomień, chyba że masz udowodnione gwarancje, że utracone pobudki są niemożliwe.
Typowym przykładem jest współbieżna kolejka FIFO, w której: wiele modułów kolejkujących (1. i 3. powyżej) może przenieść kolejkę z pustej na niepustą. Wiele kolejek kolejkowych (2. powyżej) może czekać na warunek „kolejka nie jest pusta” pusta -> niepuste powinny powiadomić osoby odpowietrzające
Możesz łatwo napisać przeplot operacji, w którym, zaczynając od pustej kolejki, 2 kolejki kolejkowe i 2 kolejki kolejkowe współdziałają, a jedna kolejka kolejowa pozostanie w stanie uśpienia.
Jest to problem prawdopodobnie porównywalny z problemem impasu.
źródło
notify()
obudzi jeden wątek, anotifyAll()
obudzi wszystko. O ile mi wiadomo, nie ma środkowej płaszczyzny. Ale jeśli nie jesteś pewien, conotify()
zrobi z twoimi wątkami, użyjnotifyAll()
. Działa jak urok za każdym razem.źródło
Wszystkie powyższe odpowiedzi są poprawne, o ile mogę powiedzieć, więc powiem ci coś jeszcze. W przypadku kodu produkcyjnego naprawdę powinieneś użyć klas w java.util.concurrent. Jest niewiele rzeczy, których nie mogą dla Ciebie zrobić, jeśli chodzi o współbieżność w Javie.
źródło
notify()
pozwala pisać bardziej wydajny kod niżnotifyAll()
.Rozważ następujący fragment kodu, który jest wykonywany z wielu równoległych wątków:
Można to uczynić bardziej wydajnym, używając
notify()
:W przypadku dużej liczby wątków lub jeśli ocena pętli oczekiwania jest kosztowna,
notify()
będzie ona znacznie szybsza niżnotifyAll()
. Na przykład, jeśli masz 1000 wątków, 999 wątków zostanie przebudzonych i ocenionych po pierwszymnotifyAll()
, a następnie 998, a następnie 997 i tak dalej. Przeciwnie, w przypadkunotify()
rozwiązania obudzi się tylko jeden wątek.Użyj,
notifyAll()
gdy musisz wybrać, który wątek wykona następnie pracę:Na koniec ważne jest, aby zrozumieć, że w przypadku przebudzonego
notifyAll()
kodu wsynchronized
blokach będzie on wykonywany sekwencyjnie, a nie jednocześnie. Powiedzmy, że w powyższym przykładzie czekają trzy wątki i wywołania czwartego wątkunotifyAll()
. Wszystkie trzy wątki zostaną przebudzone, ale tylko jeden rozpocznie wykonywanie i sprawdzi stanwhile
pętli. Jeśli warunek jest spełnionytrue
, zadzwoniwait()
ponownie, i dopiero wtedy drugi wątek zacznie działać i sprawdziwhile
stan pętli itd.źródło
Oto prostsze wyjaśnienie:
Masz rację, niezależnie od tego, czy użyjesz replace (), czy notyfikujAll (), natychmiastowy wynik jest taki, że dokładnie jeden inny wątek przejmie monitor i rozpocznie wykonywanie. (Zakładając, że niektóre wątki zostały w rzeczywistości zablokowane podczas oczekiwania () dla tego obiektu, inne niezwiązane wątki nie pochłaniają wszystkich dostępnych rdzeni itp.) Wpływ pojawia się później.
Załóżmy, że wątki A, B i C czekają na ten obiekt, a wątek A pobiera monitor. Różnica polega na tym, co dzieje się, gdy A zwolni monitor. Jeśli użyłeś powiadomienia (), wówczas B i C nadal są blokowane w funkcji wait (): nie czekają na monitorze, czekają na powiadomienie. Kiedy A zwolni monitor, B i C nadal będą tam siedzieć i czekać na powiadomienie ().
Jeśli użyto funkcji replaceAll (), zarówno B, jak i C przeszły obok stanu „czekaj na powiadomienie” i oba czekają na uzyskanie monitora. Kiedy A zwolni monitor, B lub C go przejmie (zakładając, że żaden inny wątek nie konkuruje o ten monitor) i rozpocznie wykonywanie.
źródło
Ta odpowiedź jest graficznym przepisaniem i uproszczeniem doskonałej odpowiedzi xagyg , w tym komentarzy Erana .
Dlaczego warto korzystać z funkcji powiadomień, nawet jeśli każdy produkt jest przeznaczony dla jednego konsumenta?
Rozważ producentów i konsumentów, uproszczonych w następujący sposób.
Producent:
Konsument:
Załóżmy, że 2 producentów i 2 konsumentów dzieli bufor o rozmiarze 1. Poniższy obraz przedstawia scenariusz prowadzący do impasu , którego można by uniknąć, gdyby wszystkie użyte wątki powiadomiły AllAll .
Każde powiadomienie jest oznaczone budzącym się wątkiem.
źródło
Chciałbym wspomnieć o tym, co zostało wyjaśnione w praktyce Java Concurrency w praktyce:
Pierwszy punkt, czy powiadomić czy powiadomić wszystkie?
Ten problem można rozwiązać za pomocą obiektu warunku jawnego blokowania blokady, dostarczonego w jdk 5, ponieważ zapewnia on inne oczekiwanie na predykat każdego 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
źródło
notify()
- Wybiera losowy wątek z zestawu oczekiwania obiektu i ustawia go wBLOCKED
stanie. Pozostałe wątki w zestawie oczekiwania obiektu są nadal wWAITING
stanie.notifyAll()
- Przenosi wszystkie wątki z zestawu oczekiwania obiektu doBLOCKED
stanu. Po użyciunotifyAll()
nie ma żadnych wątków w zestawie oczekiwania obiektu udostępnionego, ponieważ wszystkie są teraz wBLOCKED
stanie, a nie wWAITING
stanie.BLOCKED
- zablokowany w celu uzyskania blokady.WAITING
- oczekiwanie na powiadomienie (lub zablokowane na zakończenie dołączenia).źródło
Zaczerpnięte z bloga na Effective Java:
Tak więc rozumiem (z wyżej wspomnianego bloga, komentarz „Yann TM” na temat przyjętej odpowiedzi i dokumentów Java ):
źródło
Spójrz na kod opublikowany przez @xagyg.
Załóżmy, że dwa różne gwinty czeka na dwóch różnych warunkach: pierwszy gwint czeka , a drugi gwint czeka
buf.size() != MAX_SIZE
buf.size() != 0
.Załóżmy, że w pewnym momencie
buf.size()
nie jest równy 0 . JVM wywołujenotify()
zamiastnotifyAll()
, a pierwszy wątek jest powiadamiany (nie drugi).Pierwszy wątek jest budzony, sprawdza,
buf.size()
które mogą wrócićMAX_SIZE
i wraca do oczekiwania. Drugi wątek nie jest budzony, nadal czeka i nie dzwoniget()
.źródło
notify()
budzi pierwszy wątek, który wywołałwait()
ten sam obiekt.notifyAll()
budzi wszystkie wątki, które wywołaływait()
ten sam obiekt.Wątek o najwyższym priorytecie zostanie uruchomiony jako pierwszy.
źródło
notify()
gdy nie jest to dokładnie „ pierwszy wątek ”.powiadomienie powiadomi tylko jeden wątek, który jest w stanie oczekiwania, a powiadomienie wszystkich spowoduje powiadomienie wszystkich wątków w stanie oczekiwania, teraz wszystkie zgłoszone wątki i wszystkie zablokowane wątki kwalifikują się do blokady, z których tylko jeden otrzyma zamek i wszystkie pozostałe (w tym te, które są w stanie oczekiwania wcześniej) będą w stanie zablokowanym.
źródło
Podsumowując powyższe doskonałe szczegółowe wyjaśnienia oraz w najprostszy sposób, jaki mogę wymyślić, wynika to z ograniczeń wbudowanego monitora JVM, który 1) jest nabywany w całej jednostce synchronizacji (bloku lub obiekcie) i 2) nie dyskryminuje konkretnego warunku, na który czeka się / powiadamia się o / o.
Oznacza to, że jeśli wiele wątków czeka na różne warunki i zostanie użyta opcja notyfikacji (), wybrany wątek może nie być tym, który zrobiłby postęp w nowo spełnionym warunku - powodując ten wątek (i inne aktualnie oczekujące wątki, które byłyby w stanie aby spełnić warunek itp.), aby nie być w stanie robić postępów, i ostatecznie głodować lub zawiesić się program.
W przeciwieństwie do tego, enableAll () umożliwia wszystkim oczekującym wątkom ponowne przejęcie blokady i sprawdzenie ich odpowiedniego stanu, umożliwiając w końcu dokonanie postępu.
Tak więc powiadomienie () może być bezpiecznie używane tylko wtedy, gdy gwarantowany jest każdy oczekujący wątek, który umożliwi dokonanie postępu, jeśli zostanie on wybrany, co zasadniczo jest spełnione, gdy wszystkie wątki w tym samym monitorze sprawdzają tylko jeden i ten sam warunek - dość rzadki w rzeczywistych aplikacjach.
źródło
Gdy wywołasz funkcję wait () „obiektu” (oczekując, że uzyskano blokadę obiektu), stażysta zwolni blokadę tego obiektu i pomoże innym wątkom zablokować ten „obiekt”, w tym scenariuszu wystąpi więcej niż 1 wątek czeka na „zasób / obiekt” (biorąc pod uwagę, że inne wątki wydały również oczekiwanie na ten sam powyższy obiekt i po drodze pojawi się wątek, który wypełni zasób / obiekt i wywoła powiadomienie / powiadomienie wszystkich).
Tutaj, gdy wydasz powiadomienie o tym samym obiekcie (z tej samej / drugiej strony procesu / kodu), zwolni to zablokowany i oczekujący pojedynczy wątek (nie wszystkie oczekujące wątki - ten zwolniony wątek zostanie wybrany przez wątek JVM Harmonogram i cały proces uzyskiwania blokady na obiekcie jest taki sam jak zwykły).
Jeśli masz tylko jeden wątek, który będzie współużytkować / pracować nad tym obiektem, możesz użyć samej metody replace () w implementacji oczekiwania-powiadomienia.
jeśli jesteś w sytuacji, gdy więcej niż jeden wątek czyta i pisze na zasobach / obiekcie w oparciu o logikę biznesową, powinieneś przejść do powiadomieniaAll ()
teraz patrzę, jak dokładnie jvm identyfikuje i przerywa oczekujący wątek, gdy wydajemy powiadomienie () na obiekcie ...
źródło
Chociaż istnieją pewne solidne odpowiedzi powyżej, jestem zaskoczony liczbą nieporozumień i nieporozumień, które przeczytałem. Prawdopodobnie świadczy to o tym, że należy używać java.util.concurrent tak często, jak to możliwe, zamiast próbować pisać własny zepsuty współbieżny kod. Wracając do pytania: podsumowując, najlepszą praktyką jest dzisiaj UNIKANIE powiadomienia () we WSZYSTKICH sytuacjach z powodu problemu z utratą budzenia. Każdy, kto tego nie rozumie, nie powinien mieć prawa pisać kodu współbieżności o znaczeniu krytycznym. Jeśli martwisz się problemami z pasterstwem, bezpiecznym sposobem na przebudzenie jednego wątku naraz jest: 1. Zbudowanie wyraźnej kolejki oczekujących na wątki oczekujące; 2. Niech każdy wątek w kolejce zaczeka na swojego poprzednika; 3. Niech każde wywołanie wątku powiadomiAll () po zakończeniu. Lub możesz użyć Java.util.concurrent. *,
źródło
Runnable
implementacja) przetwarza zawartość kolejki. Jestwait()
on następnie używany, gdy kolejka jest pusta. Inotify()
jest wywoływany, gdy informacje są dodawane. -> w takim przypadku jest tylko jeden wątek, który kiedykolwiek wywołuje ten wątekwait()
, więc czy nie wygląda to trochę głupio,notifyAll()
jeśli wiesz, że jest tylko jeden wątek.Przebudzenie wszystkiego nie ma tu większego znaczenia. poczekaj powiadom i powiadom wszystkie, wszystkie są umieszczane po posiadaniu monitora obiektu. Jeśli wątek jest na etapie oczekiwania i wywołane zostanie powiadomienie, wątek przejmie blokadę i żaden inny wątek w tym momencie nie będzie mógł go zablokować. Dlatego równoczesny dostęp nie może mieć miejsca. O ile mi wiadomo, każde połączenie oczekujące powiadomienie i powiadomienie można wykonać dopiero po przejęciu blokady obiektu. Popraw mnie, jeśli się mylę.
źródło