Why must wait () always be in synchronized block

Wszyscy wiemy, że aby przywołać Object.wait(), to wywołanie musi być umieszczone w zsynchronizowanym bloku, w przeciwnym razie IllegalMonitorStateException jest wyrzucony. Ale jaki jest powód wprowadzenia tego ograniczenia? wiem, że wait() zwalnia monitor, ale dlaczego musimy jawnie nabyć monitor poprzez zsynchronizowanie określonego bloku, a następnie zwolnić monitor przez wywołanie wait()?

Jakie jest potencjalne uszkodzenie, jeśli możliwe było wywołanie wait() poza zsynchronizowany blok, zachowując jego semantykę-zawieszenie wątku rozmówcy?

Author: Joachim Sauer, 2010-05-06

10 answers

A wait() ma sens tylko wtedy, gdy istnieje również notify(), więc zawsze chodzi o komunikację między wątkami, a to wymaga synchronizacji, aby działać poprawnie. Można argumentować, że powinno to być niejawne, ale to naprawdę nie pomoże, z następującego powodu: {]}

Semantycznie, nigdy tylko wait(). Potrzebujesz jakiegoś warunku, aby być usatysfakcjonowanym, a jeśli nie, poczekaj, aż tak będzie. Więc to, co naprawdę robisz, to

if(!condition){
    wait();
}

Ale warunek jest ustawiany przez osobny wątek, więc w aby to działało poprawnie, potrzebujesz synchronizacji.

Jeszcze kilka rzeczy nie tak z tym, gdzie to, że Twój wątek przestał czekać, nie oznacza, że warunek, którego szukasz, jest prawdziwy:

  • Możesz uzyskać fałszywe przebudzenia (co oznacza, że wątek może obudzić się z czekania bez otrzymania powiadomienia) lub

  • Warunek może zostać ustawiony, ale trzeci wątek powoduje, że warunek jest fałszywy, zanim wątek oczekujący się obudzi (i odzyskuje monitor).

Aby poradzić sobie z tymi przypadkami, naprawdę potrzebujesz zawsze jakiejś odmiany tego:

synchronized(lock){
    while(!condition){
        lock.wait();
    }
}

Jeszcze lepiej, nie zadzieraj z prymitywami synchronizacji w ogóle i pracuj z abstrakcjami oferowanymi w pakietach java.util.concurrent.

 243
Author: Michael Borgwardt,
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-07 18:03:12

Jakie jest potencjalne uszkodzenie, jeśli możliwe było wywołanie wait() poza zsynchronizowanym blokiem, zachowując jego semantykę-zawieszając wątek wywołujący?

Zilustrujmy jakie problemy napotkalibyśmy, gdyby wait() można było wywołać poza zsynchronizowanym blokiem za pomocąkonkretnego przykładu .

Załóżmy, że mamy zaimplementować kolejkę blokującą (wiem, jest już taka w API :)

Pierwsza próba (bez synchronizacja) może wyglądać coś w linii poniżej

class BlockingQueue {
    Queue<String> buffer = new LinkedList<String>();

    public void give(String data) {
        buffer.add(data);
        notify();                   // Since someone may be waiting in take!
    }

    public String take() throws InterruptedException {
        while (buffer.isEmpty())    // don't use "if" due to spurious wakeups.
            wait();
        return buffer.remove();
    }
}

Oto, co może się potencjalnie wydarzyć:

  1. Wątek konsumencki wywołuje take() i widzi, że buffer.isEmpty().

  2. Zanim wątek konsumencki zadzwoni wait(), pojawia się wątek producenta i wywołuje pełne give(), czyli, buffer.add(data); notify();

  3. Wątek konsumencki będzie teraz wywoływał wait() (i miss notify(), który właśnie został wywołany).

  4. Jeśli niestety wątek producenta nie wyprodukuje więcej give(), ponieważ wątek konsumencki nigdy się nie budzi, a my mamy martwy zamek.

Gdy zrozumiesz Problem, Rozwiązanie jest oczywiste: użyj synchronized, aby upewnić się, że notify nigdy nie zostanie wywołana pomiędzy isEmpty a wait.

Bez wchodzenia w szczegóły: ten problem z synchronizacją jest uniwersalny. Jak wskazuje Michael Borgwardt, wait / notify polega na komunikacji między wątkami, więc zawsze skończysz z stan rasy podobny do opisanego powyżej. Z tego powodu obowiązuje zasada "only wait inside synchronized".


Akapit z linku zamieszczonego przez @ Willie podsumowuje to całkiem dobrze:

Potrzebujesz absolutnej gwarancji, że kelner i zgłaszający zgadzają się co do stanu orzeczenia. Kelner sprawdza stan predykatu w pewnym momencie nieznacznie, zanim przejdzie w stan uśpienia, ale poprawność zależy od tego, czy predykat jest prawdziwy Kiedy idzie spać. Pomiędzy tymi dwoma zdarzeniami jest okres podatności, który może złamać program.

Orzeczenie, które producent i konsument muszą uzgodnić, znajduje się w powyższym przykładzie buffer.isEmpty(). Umowa zostaje rozwiązana poprzez zapewnienie, że operacje wait I notify są wykonywane w blokach synchronized.


Ten post został przepisany jako artykuł tutaj: Java: Why wait must be called in a synchronized block

 289
Author: aioobe,
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
2018-11-25 14:32:35

@Rollerball ma rację. Wywołanie wait() jest wywołane tak, że wątek może czekać na jakiś warunek, gdy to wywołanie wait() się stanie, wątek jest zmuszony zrezygnować z blokady.
Aby z czegoś zrezygnować, najpierw musisz to posiadać. Wątek musi najpierw posiadać zamek. Stąd potrzeba wywołania go wewnątrz synchronized metoda / blok.

Tak, zgadzam się ze wszystkimi powyższymi odpowiedziami dotyczącymi potencjalnych uszkodzeń / niespójności, jeśli nie sprawdziłeś warunku w synchronized metoda / blok. Jednak jako @shrini1000 zwrócił uwagę, samo wywołanie wait() w bloku synchronizowanym nie zapobiegnie tej niespójności.

Oto przyjemna lektura..

 13
Author: ashoka.devanampriya,
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-21 07:17:42

Problem może spowodować, jeśli nie synchronizować przed wait() jest następujący:

  1. jeśli pierwszy wątek przechodzi do makeChangeOnX() i sprawdza warunek while I jest true (x.metCondition() zwraca false, czyli x.condition jest false), więc dostanie się do środka. Następnie, tuż przed metodą wait(), kolejny wątek przechodzi do setConditionToTrue() i ustawia x.condition na true i notifyAll().
  2. wtedy dopiero po tym, 1. Wątek wejdzie w jego metodę wait() (bez wpływu na notifyAll(), które zdarzyło się kilka chwilę wcześniej). W tym przypadku pierwszy wątek będzie czekał na kolejny wątek do wykonania setConditionToTrue(), ale to może się nie powtórzyć.

Ale jeśli umieścisz synchronized przed metodami, które zmieniają stan obiektu, to tak się nie stanie.

class A {

    private Object X;

    makeChangeOnX(){
        while (! x.getCondition()){
            wait();
            }
        // Do the change
    }

    setConditionToTrue(){
        x.condition = true; 
        notifyAll();

    }
    setConditionToFalse(){
        x.condition = false;
        notifyAll();
    }
    bool getCondition(){
        return x.condition;
    }
}
 4
Author: Shay.G,
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-07-17 15:15:00

Wszyscy wiemy, że metody wait (), notify() i notifyAll() są używane dla inter-threaded komunikacja. Aby pozbyć się nieodebranych sygnałów i fałszywych problemów z przebudzeniem, wątek oczekiwania zawsze czeka na pewne warunki. np. -

boolean wasNotified = false;
while(!wasNotified) {
    wait();
}

Wtedy notifying thread sets wasNotified variable to true and notify.

Każdy wątek ma swoją lokalną pamięć podręczną, więc wszystkie zmiany są najpierw tam zapisywane i następnie stopniowo awansował do pamięci głównej.

Gdyby te metody nie były wywoływane w blok zsynchronizowany, zmienna nie została nie zostanie spłukana do pamięci głównej i będzie tam w lokalnej pamięci podręcznej wątku więc wątek oczekujący będzie czekał na sygnał, chociaż został zresetowany przez powiadomienie nić.

Aby rozwiązać tego typu problemy, metody te są zawsze wywoływane wewnątrz zsynchronizowanego bloku co zapewnia, że po uruchomieniu synchronizowanego bloku wszystko będzie odczytywane z głównego pamięci i zostaną spłukane do pamięci głównej przed zakończeniem zsynchronizowanego blok.

synchronized(monitor) {
    boolean wasNotified = false;
    while(!wasNotified) {
        wait();
    }
}
Dzięki, mam nadzieję, że to wyjaśni.
 3
Author: Aavesh Yadav,
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-12-01 16:47:58

Ma to zasadniczo związek z architekturą sprzętową (tj. RAM i pamięci podręczne ).

Jeśli nie używasz synchronized razem z wait() lub notify(), inny wątek Może wprowadzić ten sam blok, zamiast czekać, aż monitor go wprowadzi. Co więcej, gdy np. uzyskuje dostęp do tablicy bez zsynchronizowanego bloku, inny wątek może nie widzieć zmiany do niej...właściwie inny wątek nie będzie widział żadnych zmian w nim Gdy ma już Kopia tablicy w buforze X-level (Alias 1st/2nd/3rd-level cache) wątku obsługującego rdzeń CPU.

Ale zsynchronizowane bloki to tylko jedna strona medalu: jeśli rzeczywiście uzyskasz dostęp do obiektu w zsynchronizowanym kontekście z niezsynchronizowanego kontekstu, obiekt nadal nie będzie zsynchronizowany nawet w zsynchronizowanym bloku, ponieważ posiada własną kopię obiektu w swoim buforze. Pisałem o tym tutaj: https://stackoverflow.com/a/21462631 I Gdy a lock przechowuje niekończący się obiekt, czy odniesienie do obiektu może być jeszcze zmienione przez inny wątek?

Ponadto jestem przekonany, że pamięci podręczne poziomu x są odpowiedzialne za większość nie odtwarzalnych błędów wykonawczych. To dlatego, że programiści zwykle nie uczą się rzeczy niskiego poziomu, jak na przykład jak działa procesor lub jak hierarchia pamięci wpływa na działanie aplikacji: {24]} http://en.wikipedia.org/wiki/Memory_hierarchy

Pozostaje zagadką dlaczego klasy programowania nie zacznij od hierarchii pamięci i architektury procesora. "Hello world" tu nie pomoże. ;)

 1
Author: Marcus,
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-05-23 12:26:38

zgodnie z docs:

Bieżący wątek musi posiadać monitor tego obiektu. Thread releases własność tego monitora.

wait() metoda oznacza po prostu, że zwalnia blokadę na obiekcie. Tak więc obiekt zostanie zablokowany tylko w obrębie zsynchronizowanego bloku/metody. Jeśli wątek znajduje się poza blokiem synchronizacji, oznacza to, że nie jest zablokowany, jeśli nie jest zablokowany, to co wydałbyś na obiekcie?

 1
Author: Arun Raaj,
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
2019-06-03 07:29:34

Bezpośrednio z tego java Oracle tutorial:

Gdy wątek wywołuje d.wait, musi posiadać wewnętrzny zamek dla d - w przeciwnym razie zostanie wyrzucony błąd. Wywołanie oczekiwania wewnątrz zsynchronizowanego metoda jest prostym sposobem uzyskania wewnętrznego zamka.

 0
Author: Rollerball,
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-05-27 07:30:47

Gdy wywołujesz notify() z obiektu T, java powiadamia o tym konkretną 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, aby powiadomić konkretną t.wait ().

 0
Author: lakshman reddy,
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-03-10 08:23:53

Thread wait na obiekcie monitorującym (obiekcie używanym przez blok synchronizacji), może być n liczba obiektów monitorujących w całej podróży pojedynczego wątku. Jeśli wątek czeka poza blokiem synchronizacji, to nie ma obiektu monitorowania, a także innego wątku powiadamiającego o dostępie do obiektu monitorowania, więc w jaki sposób wątek poza blokiem synchronizacji wiedziałby, że został powiadomiony. Jest to również jeden z powodów, dla których wait (), notify() i notifyAll() są w Klasa obiektu, a nie klasa wątku.

Zasadniczo obiekt monitorowania jest wspólnym zasobem dla wszystkich wątków, a obiekty monitorowania mogą być dostępne tylko w bloku synchronizacji.

class A {
   int a = 0;
  //something......
  public void add() {
   synchronization(this) {
      //this is your monitoring object and thread has to wait to gain lock on **this**
       }
  }
 0
Author: aditya lath,
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
2020-06-21 18:16:49