Perché deve attendere () essere sempre nel blocco sincronizzato

Sappiamo tutti che per richiamare Object.wait() , questa chiamata deve essere posizionata nel blocco sincronizzato, altrimenti viene generata una IllegalMonitorStateException . Ma qual è la ragione per fare questa restrizione? So che wait() rilascia il monitor, ma perché è necessario acquisire in modo esplicito il monitor rendendo il particolare blocco sincronizzato e quindi rilasciare il monitor chiamando wait() ?

Qual è il danno potenziale se fosse ansible invocare wait() al di fuori di un blocco sincronizzato, conservando la sua semantica – sospendendo il thread del chiamante?

Un wait() ha senso solo quando c’è anche un notify() , quindi è sempre sulla comunicazione tra thread e che ha bisogno di sincronizzazione per funzionare correttamente. Si potrebbe sostenere che ciò dovrebbe essere implicito, ma ciò non sarebbe di grande aiuto, per il seguente motivo:

Semanticamente, non wait() mai wait() . Hai bisogno di una condizione per essere satsified, e se non lo è, aspetta fino a quando non lo è. Quindi quello che fai veramente è

 if(!condition){ wait(); } 

Ma la condizione è impostata da un thread separato, quindi per avere questo lavoro correttamente è necessaria la sincronizzazione.

Un paio di altre cose che non vanno, in cui solo perché il thread interrompe l’attesa non significa che la condizione che stai cercando sia vera:

  • È ansible ottenere wakeup spurie (il che significa che un thread può svegliarsi dall’attesa senza aver mai ricevuto una notifica) o

  • La condizione può essere impostata, ma un terzo thread ritriggers la condizione al momento in cui il thread in attesa si ritriggers (e riacquisisce il monitor).

Per affrontare questi casi, ciò di cui hai veramente bisogno è sempre qualche variazione di questo:

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

Meglio ancora, non scherzare con le primitive di sincronizzazione e lavorare con le astrazioni offerte nei pacchetti java.util.concurrent .

Qual è il danno potenziale se fosse ansible invocare wait() al di fuori di un blocco sincronizzato, conservando la sua semantica – sospendendo il thread del chiamante?

Illustriamo quali problemi dovremmo incontrare se wait() possa essere chiamato al di fuori di un blocco sincronizzato con un esempio concreto .

Supponiamo di dover implementare una coda di blocco (lo so, ce n’è già una nell’API 🙂

Un primo tentativo (senza sincronizzazione) potrebbe sembrare qualcosa nella seguente riga

 class BlockingQueue { Queue buffer = new LinkedList(); 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(); } } 

Questo è ciò che potrebbe potenzialmente accadere:

  1. Un thread di consumo chiama take() e vede che il buffer.isEmpty() .

  2. Prima che il thread consumer continui a chiamare wait() , un thread del produttore arriva e invoca un full give() , cioè buffer.add(data); notify(); buffer.add(data); notify();

  3. Il thread del consumatore chiamerà ora wait() (e mancherà la notify() che è stata appena chiamata).

  4. Se sfortunato, il thread del produttore non produrrà più give() a causa del fatto che il thread del consumatore non si triggers mai e abbiamo un dead-lock.

Una volta compreso il problema, la soluzione è ovvia: esegui sempre give / notify e isEmpty / wait isEmpty .

Senza entrare nei dettagli: questo problema di sincronizzazione è universale. Come sottolinea Michael Borgwardt, l’attesa / notifica riguarda la comunicazione tra i thread, quindi finirai sempre con una condizione di competizione simile a quella descritta sopra. Questo è il motivo per cui viene applicata la regola “only wait inside synchronized”.


Un paragrafo dal link pubblicato da @ Willie lo riassume piuttosto bene:

Hai bisogno di una garanzia assoluta che il cameriere e il notificante concordino sullo stato del predicato. Il cameriere controlla a un certo punto lo stato del predicato PRIMA che vada a dormire, ma dipende dal fatto che il predicato sia vero QUANDO va a dormire. C’è un periodo di vulnerabilità tra questi due eventi, che può interrompere il programma.

Il predicato che il produttore e il consumatore devono concordare è nell’esempio precedente buffer.isEmpty() . E il contratto viene risolto assicurando che l’attesa e la notifica vengano eseguite in blocchi synchronized .


Questo post è stato riscritto come articolo qui: Java: Perché aspettare deve essere chiamato in un blocco sincronizzato

@Rollerball ha ragione. Viene chiamato wait() , in modo che il thread possa attendere il verificarsi di alcune condizioni quando si verifica questa chiamata wait() , il thread è costretto a rinunciare al proprio blocco.
Per rinunciare a qualcosa, devi prima possederlo. Il thread deve prima possedere il blocco. Da qui la necessità di chiamarlo all’interno di un metodo / blocco synchronized .

Sì, sono d’accordo con tutte le risposte sopra per quanto riguarda i potenziali danni / incongruenze se non hai controllato la condizione all’interno del metodo / blocco synchronized . Tuttavia, come ha sottolineato @ shrini1000, il solo richiamo di wait() all’interno del blocco sincronizzato non eviterà questa incoerenza.

Ecco una bella lettura ..

Il problema che potrebbe causare se non si effettua la sincronizzazione prima di wait() è il seguente:

  1. Se il primo thread entra in makeChangeOnX() e verifica la condizione while, ed è true ( x.metCondition() restituisce false , significa che x.condition è false ) quindi entrerà in esso. Quindi, appena prima del metodo wait() , un altro thread passa a setConditionToTrue() e imposta x.condition su true e notifyAll() .
  2. Quindi solo dopo, il primo thread entrerà nel suo metodo wait() (non influenzato da notifyAll() che è successo qualche istante prima). In questo caso, il primo thread rimarrà in attesa di un altro thread per eseguire setConditionToTrue() , ma ciò potrebbe non accadere di nuovo.

Ma se si mettono synchronized prima dei metodi che modificano lo stato dell’object, ciò non accadrà.

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

Sappiamo tutti che i metodi wait (), notify () e notifyAll () sono utilizzati per le comunicazioni inter-thread. Per sbarazzarsi dei segnali persi e dei problemi di triggerszione spuri, il thread in attesa attende sempre alcune condizioni. per esempio-

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

Quindi notificare i set di thread era la variabile notificata su true e notificare.

Ogni thread ha la propria cache locale, quindi tutte le modifiche vengono dapprima scritte e quindi promosse gradualmente nella memoria principale.

Se questi metodi non fossero stati richiamati all’interno del blocco sincronizzato, la variabile wasNotified non verrebbe svuotata nella memoria principale e sarebbe presente nella cache locale del thread in modo che il thread in attesa continui ad attendere il segnale sebbene sia stato ripristinato notificando il thread.

Per risolvere questi tipi di problemi, questi metodi vengono sempre richiamati all’interno del blocco sincronizzato che assicura che all’avvio del blocco sincronizzato tutto verrà letto dalla memoria principale e verrà scaricato nella memoria principale prima di uscire dal blocco sincronizzato.

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

Grazie, spero che chiarisca.

direttamente da questo tutorial di java oracle:

Quando un thread richiama d.wait, deve possedere il blocco intrinseco per d – altrimenti viene generato un errore. Invocare l’attesa all’interno di un metodo sincronizzato è un modo semplice per acquisire il blocco intrinseco.

Questo ha fondamentalmente a che fare con l’architettura hardware (cioè RAM e cache ).

Se non si utilizza synchronized insieme con wait() o notify() , un altro thread potrebbe entrare nello stesso blocco invece di attendere che il monitor lo inserisca. Inoltre, ad esempio, quando si accede ad un array senza un blocco sincronizzato, un altro thread potrebbe non vedere il cambiamento ad esso … in realtà un altro thread non vedrà alcun cambiamento ad esso quando già ha una copia dell’array nella cache a livello x ( alias caches di 1 ° / 2 ° / 3 ° livello) del core CPU di gestione thread.

Ma i blocchi sincronizzati sono solo un lato della medaglia: se si accede effettivamente a un object all’interno di un contesto sincronizzato da un contesto non sincronizzato, l’object non sarà ancora sincronizzato anche all’interno di un blocco sincronizzato, poiché contiene una propria copia del object nella sua cache. Ho scritto su questi problemi qui: https://stackoverflow.com/a/21462631 e Quando un blocco contiene un object non finale, il riferimento dell’object può ancora essere modificato da un altro thread?

Inoltre, sono convinto che le cache a livello x siano responsabili della maggior parte degli errori di runtime non riproducibili. Questo perché gli sviluppatori di solito non imparano le cose di basso livello, come il modo in cui la CPU funziona o il modo in cui la gerarchia della memoria influenza l’esecuzione delle applicazioni: http://en.wikipedia.org/wiki/Memory_hierarchy

Rimane un indovinello perché le classi di programmazione non iniziano prima con la gerarchia della memoria e l’architettura della CPU. “Hello world” non sarà di aiuto qui. 😉

Quando si chiama notify () da un object t, java notifica un particolare metodo t.wait (). Ma come fa java search e notifica un particolare metodo di attesa.

java cerca solo nel blocco di codice sincronizzato che è stato bloccato dall’object t. java non può cercare l’intero codice per notificare un particolare t.wait ().