Ma wait()
to sens tylko wtedy, gdy istnieje notify()
, więc zawsze chodzi o komunikację między wątkami, a do prawidłowego działania potrzebna jest synchronizacja. Można argumentować, że powinno to być dorozumiane, ale tak naprawdę nie pomogłoby to z następującego powodu:
Semantycznie nigdy nie tylko wait()
. Potrzebujesz pewnego warunku, by zostać usatysfakcjonowanym, a jeśli tak nie jest, poczekaj, aż to nastąpi. Więc tak naprawdę to robisz
if(!condition){
wait();
}
Ale warunek jest ustawiany przez osobny wątek, więc aby poprawnie działać, potrzebujesz synchronizacji.
Jeszcze kilka rzeczy jest z tym nie tak, że fakt, że Twój wątek zakończył się, nie oznacza, że warunek, którego szukasz, jest spełniony:
Możesz uzyskać fałszywe budzenie (co oznacza, że wątek może obudzić się z oczekiwania bez otrzymywania powiadomienia) lub
Warunek można ustawić, ale trzeci wątek ponownie powoduje, że warunek ten jest fałszywy, gdy wątek oczekujący budzi się (i ponownie uzyskuje monitor).
Aby poradzić sobie z tymi przypadkami, tak naprawdę potrzebujesz zawsze jakiejś odmiany:
synchronized(lock){
while(!condition){
lock.wait();
}
}
Jeszcze lepiej, nie zadzieraj z prymitywami synchronizacji i pracuj z abstrakcjami oferowanymi w java.util.concurrent
pakietach.
Thread.interrupted()
.Zilustrujmy problemy, na które natrafilibyśmy, gdybyśmy
wait()
mogli je wywołać poza zsynchronizowanym blokiem, z konkretnym przykładem .Załóżmy, że mamy zaimplementować kolejkę blokującą (wiem, że jest już jedna w API :)
Pierwsza próba (bez synchronizacji) może wyglądać mniej więcej tak jak poniżej
Oto, co może się zdarzyć:
Wątek konsumenta wywołuje
take()
i widzi, żebuffer.isEmpty()
.Zanim wątek konsumencki zadzwoni
wait()
, pojawia się wątek producenta i wywołuje pełnygive()
, czylibuffer.add(data); notify();
Wątek konsumenta będzie teraz dzwonił
wait()
(i pomija ten,notify()
który właśnie został wywołany).W przypadku pecha wątek producenta nie będzie produkował więcej,
give()
ponieważ wątek konsumencki nigdy się nie budzi, a my mamy impas.Po zrozumieniu problemu rozwiązanie jest oczywiste: użyj,
synchronized
aby upewnić się, żenotify
nigdy nie jest wywoływane międzyisEmpty
await
.Bez wchodzenia w szczegóły: ten problem synchronizacji jest uniwersalny. Jak zauważa Michael Borgwardt, funkcja czekania / powiadamiania dotyczy komunikacji między wątkami, więc zawsze będziesz mieć wyścig podobny do opisanego powyżej. Właśnie dlatego obowiązuje zasada „tylko zsynchronizuj czekanie w środku”.
Akapit z linku opublikowanego przez @Willie całkiem dobrze to podsumowuje:
Predykat, który producent i konsument muszą uzgodnić, znajduje się w powyższym przykładzie
buffer.isEmpty()
. Umowa zostaje rozwiązana poprzez upewnienie się, że oczekiwanie i powiadomienia są wykonywane wsynchronized
blokach.Ten post został przepisany jako artykuł tutaj: Java: Dlaczego trzeba czekać w zsynchronizowanym bloku
źródło
return buffer.remove();
podczas blokowania, ale późniejwait();
, to działa?wait
zwrotach.Thread.currentThread().wait();
wmain
funkcji otoczonej try-catch dlaInterruptedException
. Bezsynchronized
bloku daje mi ten sam wyjątekIllegalMonitorStateException
. Co sprawia, że teraz osiąga stan nielegalny? Działa jednak wewnątrzsynchronized
bloku.@Rollerball ma rację. Wywoływane
wait()
jest, aby wątek mógł czekać na wystąpienie pewnego warunku, gdy towait()
wywołanie zostanie wykonane, wątek jest zmuszony zrezygnować z blokady.Aby coś zrezygnować, musisz go najpierw posiadać. Wątek musi najpierw posiadać zamek. Stąd potrzeba wywołania go w
synchronized
metodzie / bloku.Tak, zgadzam się ze wszystkimi powyższymi odpowiedziami dotyczącymi potencjalnych szkód / niespójności, jeśli nie sprawdziłeś warunku w
synchronized
metodzie / bloku. Jednak, jak zauważył @ shrini1000, samo wywołaniewait()
w obrębie zsynchronizowanego bloku nie zapobiegnie tej niespójności.Oto miła lektura ..
źródło
Problem, który może powodować, jeśli nie synchronizujesz wcześniej,
wait()
jest następujący:makeChangeOnX()
i sprawdzi warunek while, i jesttrue
(x.metCondition()
wracafalse
, znaczyx.condition
jestfalse
), więc wejdzie do niego. Następnie tuż przedwait()
sposobu, inny wątek idziesetConditionToTrue()
i ustawiax.condition
natrue
inotifyAll()
.wait()
metodę (bez wpływu na to,notifyAll()
co wydarzyło się kilka chwil wcześniej). W takim przypadku pierwszy wątek będzie czekał na wykonanie kolejnego wątkusetConditionToTrue()
, ale może się to nie powtórzyć.źródło
Wszyscy wiemy, że do komunikacji między wątkami używane są metody wait (), notyfikuj () i notyfikuj wszystkie (). Aby pozbyć się brakującego sygnału i fałszywych problemów z budzeniem, oczekująca nić zawsze czeka na pewne warunki. na przykład-
Następnie powiadamiając zestawy wątków zmienna wasNotified na true i powiadamiaj.
Każdy wątek ma swoją lokalną pamięć podręczną, więc wszystkie zmiany najpierw są tam zapisywane, a następnie stopniowo przenoszone do pamięci głównej.
Gdyby te metody nie zostały wywołane w bloku zsynchronizowanym, zmienna wasNotified nie zostałaby wpuszczona do pamięci głównej i znajdowałaby się w lokalnej pamięci podręcznej wątku, więc wątek oczekujący będzie czekał na sygnał, chociaż został zresetowany przez powiadomienie wątku.
Aby rozwiązać tego rodzaju problemy, metody te są zawsze wywoływane w bloku synchronicznym, co zapewnia, że po uruchomieniu bloku synchronicznego wszystko zostanie odczytane z pamięci głównej i zostanie opróżnione do pamięci głównej przed wyjściem z bloku synchronicznego.
Dzięki, mam nadzieję, że to wyjaśni.
źródło
Zasadniczo ma to związek z architekturą sprzętową (tj. RAM i pamięci podręczne ).
Jeśli nie używasz
synchronized
razem zwait()
lubnotify()
, inny wątek może wejść do tego samego bloku, zamiast czekać na wejście monitora. Ponadto, np. Podczas uzyskiwania dostępu do tablicy bez zsynchronizowanego bloku, inny wątek może nie zobaczyć zmiany w nim ... w rzeczywistości inny wątek nie zobaczy żadnych zmian, jeśli ma już kopię tablicy w pamięci podręcznej poziomu x ( aka 1. / 2. / 3. poziom pamięci podręcznej) rdzenia procesora obsługującego wątki.Ale zsynchronizowane bloki to tylko jedna strona medalu: jeśli faktycznie uzyskujesz dostęp do obiektu w zsynchronizowanym kontekście z niezsynchronizowanego kontekstu, obiekt nadal nie będzie synchronizowany nawet w obrębie zsynchronizowanego bloku, ponieważ zawiera własną kopię obiekt w pamięci podręcznej. Pisałem o tych problemach tutaj: https://stackoverflow.com/a/21462631 i kiedy blokada zawiera nie-końcowy obiekt, czy odwołanie do obiektu może być zmienione przez inny wątek?
Ponadto jestem przekonany, że pamięci podręczne poziomu X są odpowiedzialne za większość niepowtarzalnych błędów środowiska wykonawczego. Jest tak, ponieważ programiści zwykle nie uczą się rzeczy niskiego poziomu, takich jak działanie procesora lub jak hierarchia pamięci wpływa na działanie aplikacji: http://en.wikipedia.org/wiki/Memory_hierarchy
Pozostaje zagadką, dlaczego klasy programowania nie zaczynają się najpierw od hierarchii pamięci i architektury procesora. „Witaj świecie” tu nie pomoże. ;)
źródło
bezpośrednio z tego samouczka java oracle:
źródło
Po wywołaniu funkcji powiadomienie () z obiektu t, java powiadamia określoną metodę t.wait (). Ale w jaki sposób Java wyszukuje i powiadamia określoną metodę oczekiwania.
java sprawdza tylko zsynchronizowany blok kodu, który został zablokowany przez obiekt t. java nie może przeszukiwać całego kodu w celu powiadomienia określonego t.wait ().
źródło
zgodnie z dokumentami:
wait()
metoda oznacza po prostu zwolnienie blokady obiektu. Zatem obiekt zostanie zablokowany tylko w obrębie synchronizowanego bloku / metody. Jeśli wątek znajduje się poza blokiem synchronizacji, oznacza to, że nie jest zablokowany, jeśli nie jest zablokowany, to co byś zwolnił na obiekcie?źródło
Oczekiwanie na wątek na obiekcie monitorującym (obiekcie używanym przez blok synchronizacji). Może istnieć n obiektów monitorujących w całej podróży jednego wątku. Jeśli Wątek czeka poza blokiem synchronizacji, wówczas nie ma obiektu monitorowania, a także inny wątek powiadamia o dostępie do obiektu monitorowania, więc skąd wątek poza blokiem synchronizacji wiedziałby, że został powiadomiony. Jest to również jeden z powodów, dla których czekaj (), powiadom () i notyfikuj () są w klasie obiektowej, a nie w klasie wątków.
Zasadniczo obiekt monitorowania jest tutaj wspólnym zasobem dla wszystkich wątków, a obiekty monitorowania mogą być dostępne tylko w bloku synchronizacji.
źródło