La mia risposta ha alcune informazioni specifiche sull'implementazione. Si basa sulla mia conoscenza pratica di Sun JVM e di altri comportamenti delle librerie di thread.
Se due thread di produttore richiamano la notifica, è garantito che due thread di attesa distinti verranno risvegliati?
No, non lo è. Non c'è alcuna garanzia che ci saranno dei consumatori risvegliati. Ciò che è garantito è che se ci sono 2 thread in attesa, quindi 2 diversi thread verranno messi nella coda di esecuzione.
oppure può essere che due notify()
s sparato poco dopo l'altra causa lo stesso thread comsumer da coda per sveglia due volte?
No. Due chiamate notify()
non provocano lo stesso thread di consumo in coda due volte. Potrebbe tuttavia causare il risveglio di un thread e l'eventuale attesa di altri thread, pertanto la seconda chiamata notify()
potrebbe non fare nulla. Ovviamente il thread avrebbe potuto essere svegliato e poi sarebbe tornato indietro aspettando di nuovo e quindi ottenere la seconda chiamata notify()
in quel modo, ma non penso che sia quello che stai chiedendo.
java ha qualche operazione interna atomica per risvegliare i fili esattamente una volta?
Sì. Il codice Thread
ha un numero di punti di sincronizzazione. Una volta che un thread è stato notificato, viene spostato fuori dalla coda wait
. Le future chiamate a notify()
esamineranno la coda wait
e non troveranno il thread.
Un altro importante punto. Con i modelli produttore/consumatore, assicurati sempre di testare la condizione in un ciclo while
. Il motivo è che ci sono condizioni di gara con i consumatori che sono bloccati sulla serratura ma non aspettano le condizioni.
synchronized (workQueue) {
// you must do a while here
while (workQueue.isEmpty()) {
workQueue.wait();
}
workQueue.remove();
}
Consumer1
potrebbe essere in attesa su workQueue
. Consumer2
potrebbe essere bloccato su synchronized
ma nella coda di esecuzione. Se qualcosa viene inserito in workQueue
e workQueue.notify()
viene chiamato. Consumer2
è ora messo in coda di esecuzione ma è dietroConsumer1
chi è il primo. Questa è un'implementazione comune. Quindi Consumer1
inserisce un elemento rimosso da workQueue
a cui è stato notificato Consumer2
. Consumer2
deve eseguire nuovamente il test se workQueue
è vuoto altrimenti verrà generato remove()
perché la coda è di nuovo vuota. Vedi qui per more details of the race.
È anche importante rendersi conto che le scorte spurie sono state documentate in modo che il ciclo while
protegga contro un thread che si sta svegliando senza una chiamata wait()
.
Tutto ciò detto, se è possibile ridurre il codice produttore/consumatore utilizzando un BlockingQueue
come consigliato in altre risposte, è necessario farlo. Il codice BlockingQueue
ha già risolto tutti questi problemi.
Perché non usi semplicemente un'implementazione di BlockingQueue? –
Sono d'accordo con Mauricio. Evita l'attesa()/notify() se possibile: è un vespaio di condizioni di gara, API scarsamente documentate e "wakeup spurie". –