2016-03-12 2 views
7

Ho cercato di capire la necessità di implementare i thread utilizzando wait() e notify() quando si accede alle risorse condivise o si fa affidamento sul loro stato.Comprensione della necessità di attesa() e notifica()

Vedo che l'idea è di monitorare gli oggetti e di attendere la loro disponibilità e rilasciarli dopo l'uso per renderli utilizzabili per altri thread/metodi, ma perché sono quei metodi necessari invece di dichiarare gli oggetti rilevanti come statico volatile in modo che altri thread apprendano il cambiamento di stato senza richiamare quei metodi?

Per esempio

In un ristorante, ci sono 2 cuochi. Uno degli chef è un bravo chef (qualità di cucina migliore, ..) e porta un booleano isGoodCook = true, mentre il secondo chef è un cattivo cuoco e porta un booleano isGoodCook = false.

C'è solo l'attrezzatura per uno chef per cucinare i pasti alla volta. Il cattivo cuoco cucina per un periodo di tempo specifico (= tempo di cottura), mentre il bravo cuoco entra in cucina ogni tanto per assumere il compito del cattivo chef di cucinare i pasti. Il buon cuoco non può mai essere interrotto nel suo processo di cottura e cucina per tutta la sua cucina. Tempo dopo che ha iniziato.

(Lo chef cattivo smette di cucinare finché il bravo chef prende la parte dei pasti di cucina (= tempo di cucina del buon cuoco)).

E dopo che il bravo chef ha smesso di cucinare, il cattivo chef deve continuare il compito di preparare i pasti ancora una volta.

private boolean cooking; //is equipment used at the moment 
private boolean isGoodCook; 
private boolean cookingDesire; //indicating to chef to stop cooking 
private int cookingTime; 


public CookingTask(boolean isGoodCook, int cookingTime) 
{ 
    this.isGoodCook = isGoodCook; 
    this.cookingTime = cookingTime; 
} 

public void run() 
{ 
    if(isGoodCook) 
    { 
     //notify actual cook to stop cooking 
     cookingDesire = true; 
    } 
    //wait til equipment to cook 
    //is available 
    while(cooking) 
    { 
     try 
     { 
      wait(); 
     } catch (InterruptedException e) 
     { 
      e.printStackTrace(); 
     } 
    } 
    //reserve equipment 
    cooking = true; 
    cookingDesire = false; 
    //notify other threads (= bad cook) 
    notifyAll(); 
    startCooking(); 
} 

private void startCooking() 
{ 
    for(int i = 0; i < cookingTime; cookingTime--) 
    { 
     try 
     { 
      Thread.sleep(1000); 
      //if good chef comes in 
      if(cookingDesire) 
      { 
       //bad chef starts to pause 
       startBreak(); 
      } 
     } 
     catch (InterruptedException e) 
     { 
      e.printStackTrace(); 
     } 
    } 
    cooking = false; 
} 

public void startBreak() 
{ 
    //bad chef stops cooking 
    cooking = false; 
    notifyAll(); 
    //measure break time of bad chef 
    long stopCookingTime = System.currentTimeMillis(); 
    while(cookingTime > 0 && cooking) 
    { 
     try 
     { 
      wait(); 
     } catch (InterruptedException e) 
     { 
      e.printStackTrace(); 
     } 
    } 
    int pausedTime = toIntExact((System.currentTimeMillis() - stopCookingTime)/1000); 
    //calculate remaining cookingTime 
    cookingTime -= pausedTime; 
    cooking = true; 
    notifyAll(); 
} 

Forse qualcuno ha il tempo di leggere e poco delineare le mie idee sbagliate sul monitoraggio/wait() e notify() su più thread, mi piacerebbe molto grato!

+1

perché è possibile contrassegnare un tipo di valore volatile o _reference_ su un oggetto, non sull'oggetto stesso –

+0

Ciò ha senso e chiarisce le cose. Molte grazie! – freeprogramer233

+0

La parola chiave volatile fornisce una forma debole di sicurezza del thread. Garantisce visibilità per i tipi di dati a 4 byte (e più brevi), ma non fornisce alcuna forma di mutua esclusione o atomicità. Quando è necessario il pieno controllo della sincronizzazione, wait() e notify() sono le strutture di basso livello che lo abilitano. La maggior parte dei programmatori Java non utilizza wait() o notify() e utilizza le API di concorrenza simultanee, più robuste e più semplici da utilizzare. – scottb

risposta

4

statico significa che tutti gli oggetti di una classe condividono tali dati. Come pensi che possa essere usato un campo statico per dire qualcosa sullo stato di un oggetto thread specifico?

Immagino che si possa liberarsi dell'attesa/notifica; in un modo in cui un thread deve interrogare le proprietà di altri thread. Ma ciò significa "attività": quel thread "in attesa" deve eseguire il polling. Ovviamente, non è possibile eseguire il polling costantemente, quindi vorrai che vada in pausa per determinati periodi di tempo. Che è ... quasi come in attesa, ma più complicato, perché devi gestire tutti i dettagli sottili scrivendo il codice.

Con attesa/notifica si dispone di un modello push. Se un thread deve aspettare; tu dici di farlo; e poi, quando arriverà il momento, sarà svegliato. Questa è una semantica abbastanza chiara e diretta.

Quindi, quando si propone un modello diverso per risolvere tale problema; dovresti davvero dimostrare che il tuo modello raggiunge lo stesso obiettivo; e oltre a ciò, figura i vantaggi aggiuntivi di quel modello.

+0

In realtà ero abbastanza sicuro che l'approccio "wait" - "notify" fosse assolutamente necessario ed esisteva solo una conferma per poterlo capire chiaramente. La ringrazio per la risposta. – freeprogramer233

3

dichiarare gli oggetti rilevanti come volatili statici in modo che altri thread apprendano il cambiamento di stato senza richiamare tali metodi?

Scopo di statico e volatile è completamente diverso dal meccanismo di attesa-notifica fornito dai thread quando lo stato cambia o la condizione è soddisfatta.

Statico: indica che un campo/metodo è associato alla classe anziché al livello di istanza e non è necessario creare un oggetto per utilizzarli.

Volatile: indica alla JVM di leggere sempre l'ultimo valore di questo campo, ovvero garantisce la visibilità delle modifiche alle variabili volatili tra thread ed evita problemi di coerenza della cache.

Venendo per attendere e notificare, è un meccanismo di comunicazione utilizzato dai thread e lo vedremo da un esempio produttore-consumatore.

Il produttore mette in coda le attività che devono essere consumate/completate dal consumatore.

Inizia con lo scenario in cui un utente è in attesa di un'attività in coda (coda vuota). Il produttore mette le attività e successivamente notifica a tutti i consumatori in attesa. Questo sveglia il consumatore e inizia l'elaborazione delle attività. Senza che il consumatore deve continuare a interrogare la coda per l'attività.

Da un lato produttore, continua a mettere le attività in coda fino a quando la coda è piena e successivamente il produttore attende che lo spazio diventi disponibile in coda. Il consumatore mentre riprende l'attività e rende uno spazio libero in coda può avvisare il produttore. Senza questo produttore è necessario eseguire periodicamente il polling della coda per liberare spazio.

Quindi wait-notify sono meccanismi di comunicazione che i thread utilizzano per comunicare tra loro di qualsiasi condizione/cambiamento di stato.

Inoltre, con l'attesa e la notifica, JMM fornisce la garanzia di visibilità della memoria con il verificarsi prima della relazione.

In una nota simile, JMM garantisce che venga eseguita una scrittura su un campo volatile, prima di ogni successiva lettura di quel campo, ma non confondersi con il meccanismo di attesa-notifica fornito dai thread.

Di seguito un'immagine di Java revisited link che mostra lo schema produttore-consumatore utilizzando l'avviso di attesa. Linee tratteggiate verdi che invocano i metodi di notifica per riattivare i thread in attesa quando la condizione è soddisfatta. Potresti voler passare allo link se interessato al codice correlato.

Java wait-notify with Producer consumer pattern

+0

Quindi nel tuo esempio con un produttore e un consumatore, dove esiste un thread per ciascun produttore e consumatore, come dichiareresti la coda e il suo spazio/dimensione rimanente (variabili)? – freeprogramer233

+0

@ freeprogramer233 nella nostra coda di esempio potrebbe essere dichiarato come array e produttore e consumatore che lavorano sullo stesso array. Quindi il consumatore aspetta (scenario coda vuota) fino a che non vi è almeno un compito da elaborare e il produttore attende finché non c'è spazio libero nell'array (scenario completo di coda). –

+0

@MadhusudanaReddySunnapu Si noti che 'volatile' ha anche garanzie di visibilità piuttosto forti, a causa del fatto che esiste una relazione' happen-before' tra una scrittura volatile e una successiva lettura volatile della stessa variabile. – biziclop

1

Poiché entrambi sono pensati per scopi diversi, cerchiamo di capire differenze fondamentali.

static: una copia della variabile per tutti gli oggetti.

volatile: Ottenere il valore della variabile dalla memoria principale invece di cache dei processi

In molte applicazioni, è necessario avere la copia per oggetto piuttosto che singola copia per tutti gli oggetti. Non si dovrebbe essere vincolati utilizzando la copia a livello di classe per l'applicazione multi-thread con static volatile se la propria proposta è la via da seguire per gestire l'applicazione multi-thread.

Ora veniamo al volatile variable da soli in assenza di static. Ottenere l'ultimo valore dalla memoria principale va bene. Ma se più thread stanno cambiando il valore, non puoi garantire la coerenza.

Prendi un esempio del tuo conto bancario. Supponi che il saldo del tuo account sia di 50 dollari. Un thread sta aggiungendo il valore dal metodo deposit().L'altro thread deduce il valore dal metodo withdrawal().

Thread 1: ha ottenuto il valore del saldo di 50 dollari dalla memoria principale anziché dalla cache. Il deposito è avvenuto con 25 dollari e il saldo è diventato 75 ora. Supponiamo che il deposito non sia sincronizzato e che non siano stati utilizzati wait() e notify().

public void deposit(){ 
    //get balance : 50 
    // update : 75 
    // print : 25 (if withdrawal completes before print statement and 
     after get balance statement 
} 

Thread 2: Hai il valore del saldo 50 dollari dalla memoria principale invece di cache. Il prelievo è avvenuto per 25 dollari e l'equilibrio è diventato 25 ora.

public void withdrawal(){ 
    //get balance : 50 
    // update : 25 
    // print : 25 
} 

Basta fare un variable come volatile non aiuta. Devi sincronizzare i dati per coerenza.

Date un'occhiata qui sotto domande SE per capire i concetti meglio:

Difference between volatile and synchronized in Java

Volatile Vs Static in java

Static variable vs Volatile

+0

Grazie mille per la risposta in dettaglio. – freeprogramer233

1

Può essere possibile usare serrature e/o variabili volatili eseguire le attività di sincronizzazione che "attesa" e "notifica" sono progettate per eseguire, ma non sarebbe efficiente. Come semplice esempio dell'uso previsto, prendere in considerazione una coda di messaggi e un thread il cui compito è leggere i messaggi ed elaborarli. Se il thread non sta elaborando un messaggio e la coda è vuota, non ci sarà nulla di utile che il thread possa fare.

Sarebbe possibile avere un thread di elaborazione messaggi che ha semplicemente acquisito un blocco, controllato se la coda era vuota, e se non è stato rilasciato brevemente il blocco, quindi riacquisito, ricontrollato se la coda era vuota, ecc. ma un thread di questo tipo richiederebbe che la CPU impiegasse un'enorme quantità di tempo a controllare una coda vuota per i messaggi in sospeso quando ci sarebbe probabilmente qualcos'altro di più utile alla CPU.

Il modo più semplice per pensare a "wait" è offrire un'indicazione alla Java Virtual Machine che un thread ha determinato che non ci sarà nulla di utile da fare a meno che o fino a quando qualcun altro non indichi, tramite "notificare" ", potrebbe essere accaduto qualcosa di interessante. La JVM non è obbligata a fare nulla con questa indicazione, ma nella maggior parte dei casi impedisce che un thread che esegue una "attesa" non riceva più tempo di CPU fino a quando l'oggetto su cui è in attesa riceve una "notifica". Il thread potrebbe ricevere tempo CPU anche prima di quello [ad es. una JVM in esecuzione in un piccolo sistema embedded potrebbe non preoccuparsi di tenere traccia degli oggetti in attesa, ma legittimamente può "aspettare" sospendere un thread fino a quando la successiva "notifica" viene data a qualsiasi oggetto] ma con un thread inutilmente ricevere il tempo di CPU poche volte, in breve, prima che sia effettivamente necessario, sarebbe molto meno male che avere un thread senza necessità ricevere continuamente il tempo della CPU.