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:
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 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łuje notify
. 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 mieli while
, 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 w put
metodzie, jak i get
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 - czeka
KROK 3:
- P3 próbuje put
- sprawdza pętlę oczekiwania - już znak - czeka
KROK 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
metody
KROK 5:
- C1 jest wykonanie get
metody - dostaje char, rozmowy notify
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).