Chiaramente, notifyriattiva (qualsiasi) un thread nel set di attesa, notifyAllriattiva tutti i thread nel set di attesa. La seguente discussione dovrebbe chiarire ogni dubbio. notifyAlldovrebbe essere usato la maggior parte del tempo. Se non si è sicuri di quale utilizzare, utilizzare notifyAll. Si prega di consultare la spiegazione che segue.
Leggi attentamente e capisci. Vi prego di inviarmi una e-mail se avete domande.
Guarda produttore / consumatore (il presupposto è una classe ProducerConsumer con due metodi). È ROTTO (perché usa notify) - sì PU MAY funzionare - anche la maggior parte delle volte, ma può anche causare deadlock - vedremo perché:
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;
}
IN PRIMO LUOGO,
Perché abbiamo bisogno di un ciclo while che circonda l'attesa?
Abbiamo bisogno di un whileciclo nel caso in cui otteniamo questa situazione:
Il consumatore 1 (C1) inserisce il blocco sincronizzato e il buffer è vuoto, quindi C1 viene messo nel set di attesa (tramite la waitchiamata). Il consumatore 2 (C2) sta per immettere il metodo sincronizzato (al punto Y sopra), ma il produttore P1 inserisce un oggetto nel buffer e successivamente chiama notify. L'unico thread in attesa è C1, quindi viene svegliato e ora tenta di riacquistare il blocco dell'oggetto nel punto X (sopra).
Ora C1 e C2 stanno tentando di acquisire il blocco della sincronizzazione. Uno di questi (non deterministico) viene scelto ed entra nel metodo, l'altro viene bloccato (non in attesa - ma bloccato, nel tentativo di acquisire il blocco sul metodo). Diciamo che C2 ottiene prima il blocco. C1 sta ancora bloccando (cercando di acquisire il blocco su X). C2 completa il metodo e rilascia il blocco. Ora, C1 acquisisce il blocco. Indovina un po ', per fortuna abbiamo un whileloop, perché C1 esegue il controllo del loop (guard) e gli viene impedito di rimuovere un elemento inesistente dal buffer (C2 lo ha già!). Se non avessimo un while, otterremmo un IndexArrayOutOfBoundsExceptioncome C1 tenta di rimuovere il primo elemento dal buffer!
ADESSO,
Ok, ora perché abbiamo bisogno di notifica All?
Nell'esempio produttore / consumatore sopra sembra che possiamo cavarcela notify. Sembra così, perché possiamo dimostrare che le guardie sui circuiti di attesa per produttore e consumatore si escludono a vicenda. Cioè, sembra che non possiamo avere un thread in attesa nel putmetodo così comeget metodo, perché, affinché ciò sia vero, allora dovrebbe essere vero quanto segue:
buf.size() == 0 AND buf.size() == MAX_SIZE (supponiamo che MAX_SIZE non sia 0)
TUTTAVIA, questo non è abbastanza buono, ABBIAMO BISOGNO di usare notifyAll . Vediamo perché ...
Supponiamo di avere un buffer di dimensione 1 (per rendere l'esempio facile da seguire). I seguenti passaggi ci portano a un punto morto. Si noti che QUALSIASI momento un thread viene svegliato con notifica, può essere selezionato in modo non deterministico dalla JVM, ovvero qualsiasi thread in attesa può essere svegliato. Si noti inoltre che quando più thread bloccano l'accesso a un metodo (ovvero tentando di acquisire un blocco), l'ordine di acquisizione può essere non deterministico. Ricorda anche che un thread può essere solo in uno dei metodi alla volta: i metodi sincronizzati consentono a un solo thread di eseguire (cioè mantenere il blocco di) qualsiasi metodo (sincronizzato) nella classe. Se si verifica la seguente sequenza di eventi - risultati deadlock:
PASSAGGIO 1:
- P1 inserisce 1 carattere nel buffer
PASSAGGIO 2:
- P2 tentativi put- controlla il ciclo di attesa - già un carattere - attende
PASSAGGIO 3:
- Tentativi P3put - verifica il ciclo di attesa - già un carattere - attende
PASSAGGIO 4:
- C1 tenta di ottenere 1 carattere
- C2 tenta di ottenere 1 carattere - blocchi all'entrata nel getmetodo
- C3 tenta di ottenere 1 carattere - blocchi all'accesso al getmetodo
PASSO 5:
- C1 sta eseguendo il getmetodo - ottiene il carattere, chiama notify, esce dal metodo
- Il notifyP2 si sveglia
- MA, C2 entra nel metodo prima che P2 possa (P2 deve riacquisire il blocco), quindi P2 blocca quando si accede al putmetodo
- C2 controlla il ciclo di attesa, non più caratteri nel buffer, quindi attende
- C3 inserisce il metodo dopo C2, ma prima di P2, controlla il ciclo di attesa, non più caratteri nel buffer, quindi attende
PASSAGGIO 6:
- ADESSO: c'è P3, C2 e C3 in attesa!
- Infine P2 acquisisce il blocco, inserisce un carattere nel buffer, chiama la notifica, esce dal metodo
PASSAGGIO 7:
- La notifica di P2 attiva P3 (ricordare che è possibile riattivare qualsiasi thread)
- P3 verifica la condizione del ciclo di attesa, nel buffer è già presente un carattere, quindi attende.
- NON PIÙ FILETTI PER CHIAMARE NOTIFICARE E TRE FILETTI PERMANENTAMENTE SOSPESO!
SOLUZIONE: sostituire notifycon notifyAllnel codice produttore / consumatore (sopra).