Java: Notify () vs. NotifyAll () di nuovo


377

Se si cerca su Google "differenza tra notify()e notifyAll()", verranno visualizzate molte spiegazioni (lasciando da parte i paragrafi javadoc). Tutto si riduce al numero di thread in attesa che si stanno svegliando: uno dentro notify()e tutti dentro notifyAll().

Tuttavia (se capisco bene la differenza tra questi metodi), viene sempre selezionato solo un thread per un'ulteriore acquisizione del monitor; nel primo caso quello selezionato dalla VM, nel secondo caso quello selezionato dallo scheduler del thread di sistema. Le esatte procedure di selezione per entrambi (nel caso generale) non sono note al programmatore.

Qual è la differenza utile tra notify () e notifyAll () allora? Mi sto perdendo qualcosa?


6
Le librerie utili da utilizzare per la concorrenza si trovano nelle librerie di concorrenza. Suggerisco che siano una scelta migliore in quasi tutti i casi. La libreria di Concurency precede Java 5.0 (in cui sono stati aggiunti come standard nel 2004)
Peter Lawrey,

4
Non sono d'accordo con Peter. La libreria di concorrenza è implementata in Java e c'è un sacco di codice Java eseguito ogni volta che chiami lock (), unlock (), ecc. Puoi sparare a piedi usando la libreria di concorrenza invece di un buon vecchio synchronized, tranne che per alcuni , casi d'uso piuttosto rari.
Alexander Ryzhov,

2
Il malinteso chiave sembra essere questo: ... è sempre selezionato un solo thread per un'ulteriore acquisizione del monitor; nel primo caso quello selezionato dalla VM, nel secondo caso quello selezionato dallo scheduler del thread di sistema. L'implicazione è che sono essenzialmente gli stessi. Mentre il comportamento descritto è corretto, ciò che manca è che, nel notifyAll()caso, _ gli altri thread dopo il primo rimangono attivi e acquisiranno il monitor, uno per uno. Nelnotify caso, nessuno degli altri thread viene nemmeno svegliato. Funzionalmente sono molto diversi!
BeeOnRope,

1) Se molti thread sono in attesa su un oggetto, e notification () viene chiamato una sola volta su quell'oggetto. Tranne uno dei thread in attesa, i thread rimanenti attendono per sempre? 2) Se viene utilizzato notify (), solo uno dei tanti thread in attesa inizia l'esecuzione. Se viene utilizzato Notifyall (), tutti i thread in attesa vengono notificati ma solo uno di essi inizia l'esecuzione, quindi a che serve qui Notificaall ()?
Chetan Gowda,

@ChetanGowda Notifica di tutti i thread vs Notifica esattamente solo un thread arbitrario ha effettivamente una differenza significativa fino a quando quella differenza apparentemente sottile ma importante non ci colpisce. Quando si notifica () solo 1 thread, tutti gli altri thread saranno in stato di attesa fino a quando non riceve una notifica esplicita /segnale. Notificando tutto, tutti i thread verranno eseguiti e completati in un certo ordine uno dopo l'altro senza ulteriori notifiche - qui dovremmo dire che i thread sonoblocked e non waiting. Quando il blockedsuo exec viene temporaneamente sospeso fino a quando un altro thread non si trova all'interno del syncblocco.
user104309

Risposte:


248

Tuttavia (se capisco bene la differenza tra questi metodi), viene sempre selezionato solo un thread per un'ulteriore acquisizione del monitor.

Questo non è corretto o.notifyAll()riattiva tutti i thread bloccati nelle o.wait()chiamate. I thread possono tornare solo o.wait()uno per uno, ma ognuno avrà il proprio turno.


In poche parole, dipende dal motivo per cui i tuoi thread sono in attesa di essere avvisati. Vuoi dire a uno dei thread in attesa che è successo qualcosa o vuoi raccontarli tutti allo stesso tempo?

In alcuni casi, tutti i thread in attesa possono eseguire azioni utili al termine dell'attesa. Un esempio potrebbe essere un insieme di thread in attesa del completamento di un determinato compito; una volta terminata l'attività, tutti i thread in attesa possono continuare con la propria attività. In tal caso, dovresti utilizzare notificationAll () per riattivare tutti i thread in attesa contemporaneamente.

Un altro caso, ad esempio il blocco reciprocamente esclusivo, solo uno dei thread in attesa può fare qualcosa di utile dopo essere stato avvisato (in questo caso acquisire il blocco). In tal caso, preferiresti usare notify () . Se correttamente implementato, è possibile utilizzare NotifyAll () anche in questa situazione, ma si risveglierebbero inutilmente thread che non possono comunque fare nulla.


In molti casi, il codice per attendere una condizione verrà scritto come un ciclo:

synchronized(o) {
    while (! IsConditionTrue()) {
        o.wait();
    }
    DoSomethingThatOnlyMakesSenseWhenConditionIsTrue_and_MaybeMakeConditionFalseAgain();
}

In questo modo, se una o.notifyAll()chiamata risveglia più di un thread in attesa e il primo a tornare dalle o.wait()marche lascia la condizione nel falso stato, gli altri thread che sono stati risvegliati torneranno in attesa.


29
se si notifica solo un thread ma più sono in attesa su un oggetto, in che modo la VM determina quale deve notificare?
anfibio,

6
Non posso dire con certezza delle specifiche Java, ma in generale dovresti evitare di fare ipotesi su tali dettagli. Penso che si possa presumere che la VM lo farà in modo sano e per lo più corretto, però.
Liedman,

15
Liedman ha gravemente torto, la specifica Java afferma esplicitamente che la notifica () non è garantita per essere corretta. vale a dire che ogni chiamata a notificare potrebbe riattivare lo stesso thread (la coda dei thread nel monitor NON È FAIR o FIFO). Tuttavia, lo scheduler è garantito per essere corretto. Questo è il motivo per cui nella maggior parte dei casi in cui hai più di 2 thread dovresti preferire notificationAll.
Yann TM,

45
@YannTM Sono tutto per una critica costruttiva, ma penso che il tuo tono sia un po 'ingiusto. Ho detto esplicitamente "non posso dirlo con certezza" e "Penso". Facile, hai mai scritto qualcosa sette anni fa che non era corretto al 100%?
Liedman,

10
Il problema è che questa è la risposta accettata, non è una questione di orgoglio personale. Se sai di aver sbagliato ora, modifica la tua risposta per dirla e punta ad esempio su xagyg pedagogico e la risposta corretta di seguito.
Yann TM

330

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).


1
finnw - P3 deve ricontrollare la condizione perché le notifycause P3 (il thread selezionato in questo esempio) procedono dal punto in cui era in attesa (cioè all'interno del whileloop). Esistono altri esempi che non causano deadlock, tuttavia, in questo caso l'uso di notifynon garantisce codice senza deadlock. L'uso di notifyAllfa.
xagyg,

4
@marcus Molto vicino. Con NotifyAll, ogni thread riacquisterà il blocco (uno alla volta), ma nota che dopo che un thread ha riacquistato il blocco ed eseguito il metodo (e quindi è uscito) ... il thread successivo riacquista il blocco, controlla il "while" e tornerà ad "attendere" (a seconda di quale sia la condizione ovviamente). Quindi, avvisa riattiva un thread - come indicato correttamente. notifyAll riattiva tutti i thread e ogni thread riacquista il blocco uno alla volta - verifica le condizioni del "while" ed esegue il metodo o "attende" di nuovo.
xagyg,

1
@xagyg, stai parlando di uno scenario in cui ogni produttore ha un solo carattere da archiviare? In tal caso, il tuo esempio è corretto, ma IMO non molto interessante. Con i passaggi aggiuntivi che suggerisco, è possibile eseguire il deadlock dello stesso sistema, ma con una quantità illimitata di input, che è il modo in cui tali schemi vengono normalmente utilizzati nella realtà.
eran

3
@codeObserver Hai chiesto: "Chiamare notifyAll () porterebbe a più thread di attesa che controllano la condizione while () allo stesso tempo .. e quindi c'è la possibilità che prima che il tempo venga saitsfied, 2 thread sono già fuori di esso causando outOfBound eccezione ?." No, questo non è possibile, poiché sebbene più thread si riattivano, non possono controllare contemporaneamente la condizione while. Ognuno di loro è tenuto a riottenere il blocco (immediatamente dopo l'attesa) prima di poter rientrare nella sezione del codice e ricontrollare il tempo. Pertanto, uno alla volta.
Xagyg,

4
@xagyg bell'esempio. Questo è fuori tema rispetto alla domanda originale; solo per motivi di discussione. Il deadlock è un problema di progettazione imo (correggimi se sbaglio). Perché hai un blocco condiviso sia da put che da get. E JVM non è abbastanza intelligente da chiamare put dopo aver ottenuto il blocco e viceversa. Il dead lock si verifica perché put si sveglia un altro put, che si rimette in wait () a causa di while (). Far funzionare due classi (e due blocchi)? Quindi metti {synchonized (get)}, get {(synchonized (put)}. In altre parole, get ti sveglierà solo, e put ti sveglierà solo.
Jay

43

Differenze utili:

  • Usa un avviso () se tutti i thread in attesa sono intercambiabili (l'ordine in cui si riattivano non ha importanza) o se hai sempre e solo un thread in attesa. Un esempio comune è un pool di thread utilizzato per eseguire lavori da una coda: quando viene aggiunto un lavoro, uno dei thread viene avvisato di riattivarsi, eseguire il lavoro successivo e tornare in sospensione.

  • Utilizzare notifyAll () per altri casi in cui i thread in attesa potrebbero avere scopi diversi e dovrebbero essere in grado di essere eseguiti contemporaneamente. Un esempio è un'operazione di manutenzione su una risorsa condivisa, in cui più thread sono in attesa del completamento dell'operazione prima di accedere alla risorsa.


19

Penso che dipenda da come vengono prodotte e consumate le risorse. Se sono disponibili 5 oggetti di lavoro contemporaneamente e si hanno 5 oggetti di consumo, sarebbe opportuno riattivare tutti i thread utilizzando NotifyAll () in modo che ognuno possa elaborare 1 oggetto di lavoro.

Se hai a disposizione un solo oggetto di lavoro, che senso ha risvegliare tutti gli oggetti di consumo per correre per quell'oggetto? Il primo che controlla il lavoro disponibile lo otterrà e tutti gli altri thread controlleranno e scopriranno che non hanno nulla da fare.

Ho trovato un'ottima spiegazione qui . In breve:

Il metodo notify () viene generalmente utilizzato per i pool di risorse , dove esiste un numero arbitrario di "consumatori" o "lavoratori" che prendono risorse, ma quando una risorsa viene aggiunta al pool, solo uno dei consumatori o dei lavoratori in attesa può trattare con esso. Il metodo notifyAll () è effettivamente utilizzato nella maggior parte degli altri casi. In senso stretto, è necessario comunicare ai camerieri una condizione che potrebbe consentire a più camerieri di procedere. Ma questo è spesso difficile da sapere. Quindi, come regola generale, se non si dispone di una logica particolare per l'utilizzo di notify (), è probabile che si debba utilizzare notifyAll () , poiché spesso è difficile sapere esattamente quali thread saranno in attesa su un determinato oggetto e perché.


11

Si noti che con le utilità di concorrenza è anche possibile scegliere tra signal()e signalAll()poiché questi metodi vengono chiamati lì. Quindi la domanda rimane valida anche conjava.util.concurrent .

Doug Lea solleva un punto interessante nel suo famoso libro : se notify()e Thread.interrupt()accade allo stesso tempo, la notifica potrebbe effettivamente perdersi. Se ciò può accadere e ha implicazioni drammatiche, notifyAll()è una scelta più sicura anche se si paga il prezzo dell'overhead (svegliare troppi thread per la maggior parte del tempo).


10

Breve riassunto:

Preferisci sempre notifyAll () rispetto a notify () a meno che tu non abbia un'applicazione massicciamente parallela in cui un gran numero di thread fa la stessa cosa.

Spiegazione:

notify () [...] sveglia un singolo thread. Dato che notification () non consente di specificare il thread che è stato risvegliato, è utile solo in applicazioni massicciamente parallele, vale a dire programmi con un numero elevato di thread, tutti con compiti simili. In tale applicazione, non ti interessa quale thread viene svegliato.

fonte: https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

Confronta Notify () con NotifyAll () nella situazione sopra descritta: un'applicazione massicciamente parallela in cui i thread stanno facendo la stessa cosa. Se in questo caso si chiama notificationAll () , notifyAll () indurrà il risveglio (ovvero la pianificazione) di un numero enorme di thread, molti dei quali inutilmente (poiché solo un thread può effettivamente procedere, vale a dire il thread a cui verrà concesso il monitorare l'oggetto wait () , notify () o notifyAll () è stato chiamato), quindi sprecando risorse di elaborazione.

Pertanto, se non si dispone di un'applicazione in cui un numero enorme di thread fa la stessa cosa contemporaneamente, preferire notifyAll () rispetto a notification () . Perché? Perché, come altri utenti hanno già risposto in questo forum, notifica ()

riattiva un singolo thread in attesa sul monitor di questo oggetto. [...] La scelta è arbitraria e si verifica a discrezione dell'attuazione.

fonte: API Java SE8 ( https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify-- )

Immagina di avere un'applicazione per il consumatore del produttore in cui i consumatori sono pronti (cioè aspettano () ing) a consumare, i produttori sono pronti (cioè aspettano () ing) per produrre e la coda degli articoli (da produrre / consumare) è vuota. In tal caso, notification () potrebbe svegliare solo i consumatori e mai i produttori perché la scelta che viene svegliata è arbitraria . Il ciclo del consumatore-produttore non farebbe alcun progresso, sebbene produttori e consumatori siano pronti a produrre e consumare, rispettivamente. Invece, un consumatore viene svegliato (ovvero lasciando lo stato wait () ), non toglie un oggetto dalla coda perché è vuoto e avvisa () un altro consumatore di procedere.

Al contrario, notificationAll () risveglia sia i produttori che i consumatori. La scelta programmata dipende dallo scheduler. Naturalmente, a seconda dell'implementazione dello scheduler, lo scheduler potrebbe anche programmare solo i consumatori (ad esempio se si assegnano ai thread dei consumatori una priorità molto alta). Tuttavia, il presupposto qui è che il pericolo dello scheduler che pianifica solo i consumatori sia inferiore al pericolo che la JVM risvegli solo i consumatori perché qualsiasi programmatore ragionevolmente implementato non prende solo decisioni arbitrarie . Piuttosto, la maggior parte delle implementazioni dello scheduler fanno almeno qualche sforzo per prevenire la fame.


9

Ecco un esempio Eseguirlo. Quindi modificare uno dei notifyAll () in notification () e vedere cosa succede.

Classe ProducerConsumerExample

public class ProducerConsumerExample {

    private static boolean Even = true;
    private static boolean Odd = false;

    public static void main(String[] args) {
        Dropbox dropbox = new Dropbox();
        (new Thread(new Consumer(Even, dropbox))).start();
        (new Thread(new Consumer(Odd, dropbox))).start();
        (new Thread(new Producer(dropbox))).start();
    }
}

Classe Dropbox

public class Dropbox {

    private int number;
    private boolean empty = true;
    private boolean evenNumber = false;

    public synchronized int take(final boolean even) {
        while (empty || evenNumber != even) {
            try {
                System.out.format("%s is waiting ... %n", even ? "Even" : "Odd");
                wait();
            } catch (InterruptedException e) { }
        }
        System.out.format("%s took %d.%n", even ? "Even" : "Odd", number);
        empty = true;
        notifyAll();

        return number;
    }

    public synchronized void put(int number) {
        while (!empty) {
            try {
                System.out.println("Producer is waiting ...");
                wait();
            } catch (InterruptedException e) { }
        }
        this.number = number;
        evenNumber = number % 2 == 0;
        System.out.format("Producer put %d.%n", number);
        empty = false;
        notifyAll();
    }
}

Classe di consumo

import java.util.Random;

public class Consumer implements Runnable {

    private final Dropbox dropbox;
    private final boolean even;

    public Consumer(boolean even, Dropbox dropbox) {
        this.even = even;
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            dropbox.take(even);
            try {
                Thread.sleep(random.nextInt(100));
            } catch (InterruptedException e) { }
        }
    }
}

Classe di produzione

import java.util.Random;

public class Producer implements Runnable {

    private Dropbox dropbox;

    public Producer(Dropbox dropbox) {
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            int number = random.nextInt(10);
            try {
                Thread.sleep(random.nextInt(100));
                dropbox.put(number);
            } catch (InterruptedException e) { }
        }
    }
}

8

Da Joshua Bloch, lo stesso Guru Java nella seconda edizione di Effective Java:

"Articolo 69: preferire le utilità di concorrenza per attendere e notificare".


16
Il perché è più importante della fonte.
Pacerier,

2
@Pacerier Ben detto. Sarei più interessato a scoprire anche i motivi. Un possibile motivo potrebbe essere che attendere e notificare nella classe oggetto si basano su una variabile di condizione implicita. Quindi nell'esempio standard del produttore e del consumatore ..... sia il produttore che il consumatore aspetteranno le stesse condizioni che potrebbero portare a un punto morto, come spiegato da Xagyg nella sua risposta. Quindi un approccio migliore è usare 2 variabili di condizione come spiegato in docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/…
rahul

6

Spero che questo chiarisca alcuni dubbi.

notify () : il metodo notify () attiva un thread in attesa del blocco (il primo thread che ha chiamato wait () su quel blocco).

notifyAll () : il metodo notifyAll () riattiva tutti i thread in attesa del blocco; la JVM seleziona uno dei thread dall'elenco dei thread in attesa del blocco e riattiva quel thread.

Nel caso di un singolo thread in attesa di un blocco, non vi è alcuna differenza significativa tra notify () e notifyAll (). Tuttavia, quando è presente più di un thread in attesa del blocco, sia in notification () che notifyAll (), il thread esatto che è stato attivato è sotto il controllo di JVM e non è possibile controllare a livello di programmazione il riattivazione di un thread specifico.

A prima vista, sembra che sia una buona idea chiamare solo notification () per riattivare un thread; potrebbe sembrare inutile svegliare tutti i thread. Tuttavia, il problema con notify () è che il thread svegliato potrebbe non essere quello adatto per essere risvegliato (il thread potrebbe essere in attesa di qualche altra condizione, oppure la condizione non è ancora soddisfatta per quel thread ecc.). In tal caso , la notifica () potrebbe andare persa e nessun altro thread si risveglierà potenzialmente portando a un tipo di deadlock (la notifica viene persa e tutti gli altri thread sono in attesa di notifica — per sempre).

Per evitare questo problema , è sempre meglio chiamare notificationAll () quando c'è più di un thread in attesa di un blocco (o più di una condizione per l'attesa). Il metodo notifyAll () attiva tutti i thread, quindi non è molto efficiente. tuttavia, questa perdita di prestazioni è trascurabile nelle applicazioni del mondo reale.


6

Esistono tre stati per un thread.

  1. ATTENDERE: il thread non utilizza alcun ciclo della CPU
  2. BLOCCATO - Il thread è bloccato nel tentativo di acquisire un monitor. È possibile che stia ancora utilizzando i cicli della CPU
  3. RUNNING - Il thread è in esecuzione.

Ora, quando viene chiamato un notification (), JVM seleziona un thread e li sposta nello stato BLOCCATO e quindi nello stato RUNNING poiché non vi è competizione per l'oggetto monitor.

Quando viene chiamato a notificationAll (), JVM seleziona tutti i thread e li sposta tutti nello stato BLOCCATO. Tutti questi thread otterranno il blocco dell'oggetto su una priorità. Il thread che è in grado di acquisire prima il monitor sarà in grado di passare prima allo stato RUNNING e così via.


Solo una spiegazione fantastica.
Royatirek,

5

Sono molto sorpreso che nessuno abbia menzionato il famigerato problema del "risveglio perduto" (cercalo su Google).

Fondamentalmente:

  1. se hai più thread in attesa sulla stessa condizione e,
  2. più thread che possono farti passare dallo stato A allo stato B e,
  3. più thread che possono farti passare dallo stato B allo stato A (di solito gli stessi thread come in 1.) e,
  4. il passaggio dallo stato A a B dovrebbe notificare le discussioni in 1.

POI dovresti usare avvisare Tutti a meno che tu non abbia garanzie dimostrabili che l'impossibilità di svegliarti sia impossibile.

Un esempio comune è una coda FIFO simultanea in cui: più accodatori (1. e 3. sopra) possono passare la coda da vuoti a vuoti più dequeuers (2. sopra) possono attendere la condizione "la coda non è vuota" vuota -> non vuoto dovrebbe avvisare i dequeuers

È possibile scrivere facilmente un'interlacciamento di operazioni in cui, a partire da una coda vuota, interagiscono 2 accendini e 2 dequeuers e 1 accendino rimarrà inattivo.

Questo è probabilmente un problema paragonabile al problema del deadlock.


Mi scuso, xagyg lo spiega in dettaglio. Il nome del problema è "smarrimento della sveglia"
NickV,

@Abhay Bansal: penso che ti manchi il fatto che condition.wait () rilascia il blocco e viene riacquistato dal thread che si sveglia.
NickV,

4

notify()si sveglia un thread mentre notifyAll()si sveglia tutto. Per quanto ne so non c'è via di mezzo. Ma se non sei sicuro di cosa notify()farà ai tuoi thread, usa notifyAll(). Funziona come un fascino ogni volta.


4

Tutte le risposte di cui sopra sono corrette, per quanto posso dire, quindi ti dirò qualcos'altro. Per il codice di produzione dovresti davvero usare le classi in java.util.concurrent. C'è molto poco che non possano fare per te, nell'area della concorrenza in Java.


4

notify() ti permette di scrivere codice più efficiente di notifyAll() .

Considera il seguente pezzo di codice eseguito da più thread paralleli:

synchronized(this) {
    while(busy) // a loop is necessary here
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    notifyAll();
}

Può essere reso più efficiente usando notify():

synchronized(this) {
    if(busy)   // replaced the loop with a condition which is evaluated only once
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    notify();
}

Nel caso in cui si disponga di un numero elevato di thread o se la condizione del ciclo di attesa è costosa da valutare, notify()sarà significativamente più veloce di notifyAll(). Ad esempio, se si dispone di 1000 thread, 999 thread verranno risvegliati e valutati dopo il primo notifyAll(), quindi 998, quindi 997 e così via. Al contrario, con la notify()soluzione, verrà risvegliato un solo thread.

Utilizzare notifyAll()quando è necessario scegliere quale thread eseguirà il lavoro successivo:

synchronized(this) {
    while(idx != last+1)  // wait until it's my turn
        wait();
}
...
synchronized(this) {
    last = idx;
    notifyAll();
}

Infine, è importante capire che in caso di notifyAll(), il codice all'interno dei synchronizedblocchi che sono stati risvegliati verrà eseguito in sequenza, non tutti in una volta. Diciamo che ci sono tre thread in attesa nell'esempio sopra e il quarto thread chiama notifyAll(). Tutti e tre i thread verranno risvegliati ma solo uno inizierà l'esecuzione e verificherà le condizioni del whileloop. Se la condizione è true, chiamerà di wait()nuovo, e solo allora il secondo thread inizierà l'esecuzione e controllerà la sua whilecondizione di loop, e così via.


4

Ecco una spiegazione più semplice:

Hai ragione nel dire che, sia che usi notification () o notifyAll (), il risultato immediato è che esattamente un altro thread acquisirà il monitor e inizierà l'esecuzione. (Supponendo che alcuni thread siano stati effettivamente bloccati su wait () per questo oggetto, altri thread non correlati non stanno assorbendo tutti i core disponibili, ecc.) L'impatto viene dopo.

Supponiamo che il thread A, B e C attendessero questo oggetto e che il thread A ottenga il monitor. La differenza sta in ciò che accade quando A rilascia il monitor. Se hai utilizzato Notify (), allora B e C sono ancora bloccati in wait (): non stanno aspettando sul monitor, stanno aspettando di essere avvisati. Quando A rilascia il monitor, B e C rimarranno comunque lì, in attesa di una notifica ().

Se hai utilizzato notificationAll (), allora B e C hanno entrambi superato lo stato di "wait for notification" e sono entrambi in attesa di acquisire il monitor. Quando A rilascia il monitor, B o C lo acquisiranno (supponendo che nessun altro thread sia in competizione per quel monitor) e inizieranno l'esecuzione.


Spiegazione molto chiara. Il risultato di questo comportamento di notification () può portare a "Missed Signal" / "Missed Notification" che porta a deadlock / nessuna situazione di avanzamento dello stato dell'applicazione. P-Producer, C-Consumer P1, P2 e C2 stanno aspettando C1. C1 chiama notification () ed è destinato a un produttore, ma C2 può essere svegliato e quindi sia P1 che P2 hanno perso la notifica e aspetteranno ulteriori "notifiche" esplicite (una chiamata di notifica ()).
user104309

4

Questa risposta è una riscrittura grafica e una semplificazione dell'eccellente risposta di xagyg , inclusi i commenti di Eran .

Perché usare NotifyAll, anche quando ogni prodotto è destinato a un singolo consumatore?

Considera produttori e consumatori, semplificati come segue.

Produttore:

while (!empty) {
   wait() // on full
}
put()
notify()

Consumatore:

while (empty) {
   wait() // on empty
}
take()
notify()

Si supponga che 2 produttori e 2 consumatori condividano un buffer di dimensioni 1. L'immagine seguente mostra uno scenario che porta a un deadlock , che sarebbe evitato se tutti i thread utilizzati venissero avvisati .

Ogni notifica è etichettata con il thread in fase di riattivazione.

deadlock dovuto per avvisare


3

Vorrei menzionare ciò che è spiegato in Java Concurrency in Practice:

Primo punto, sia Notifica o Notifica tutto?

It will be NotifyAll, and reason is that it will save from signall hijacking.

Se due thread A e B sono in attesa su predicati di condizioni diverse della stessa coda di condizioni e viene richiamata la notifica, dipende da JVM a quale thread JVM notificherà.

Ora, se la notifica era pensata per il thread A e JVM notificati, il thread B si risveglierà e vedrà che questa notifica non è utile, quindi attenderà nuovamente. E il thread A non verrà mai a conoscenza di questo segnale perso e qualcuno ha dirottato la sua notifica.

Quindi, chiamare notificationAll risolverà questo problema, ma avrà di nuovo un impatto sulle prestazioni poiché notificherà tutti i thread e tutti i thread competeranno per lo stesso blocco e comporterà il cambio di contesto e quindi il caricamento sulla CPU. Ma dovremmo preoccuparci delle prestazioni solo se si sta comportando correttamente, se il comportamento in sé non è corretto, le prestazioni sono inutili.

Questo problema può essere risolto utilizzando l'oggetto Condizione del blocco esplicito del blocco, fornito in jdk 5, in quanto fornisce un'attesa diversa per ogni predicato della condizione. Qui si comporterà correttamente e non ci saranno problemi di prestazioni in quanto chiamerà il segnale e si assicurerà che solo un thread sia in attesa di quella condizione


3

notify()- Seleziona un thread casuale dal set di attesa dell'oggetto e lo mette nello BLOCKEDstato. Il resto dei thread nel set di attesa dell'oggetto è ancora nello WAITINGstato.

notifyAll()- Sposta tutti i thread dal set di attesa dell'oggetto sullo BLOCKEDstato. Dopo aver usato notifyAll(), non ci sono thread rimanenti nel set di attesa dell'oggetto condiviso perché tutti sono ora nello BLOCKEDstato e non nello WAITINGstato.

BLOCKED- bloccato per acquisizione blocco. WAITING- in attesa di notifica (o bloccato per il completamento del join).


3

Tratto dal blog su Java efficace:

The notifyAll method should generally be used in preference to notify. 

If notify is used, great care must be taken to ensure liveness.

Quindi, quello che ho capito è (dal blog di cui sopra, commento di "Yann TM" sulla risposta accettata e sui documenti Java ):

  • notify (): JVM risveglia uno dei thread in attesa su questo oggetto. La selezione del filo è fatta arbitrariamente senza equità. Quindi lo stesso thread può essere risvegliato ancora e ancora. Quindi lo stato del sistema cambia ma non si fanno progressi reali. Creando così un livelock .
  • notifyAll (): JVM risveglia tutti i thread e quindi tutti i thread corrono per il blocco su questo oggetto. Ora, lo scheduler della CPU seleziona un thread che acquisisce il blocco su questo oggetto. Questo processo di selezione sarebbe molto meglio della selezione di JVM. Quindi, garantendo vivacità.

2

Dai un'occhiata al codice pubblicato da @xagyg.

Supponiamo che due thread diversi stiano aspettando due condizioni diverse:
il primo thread è in attesa buf.size() != MAX_SIZEe il secondo thread è in attesa buf.size() != 0.

Supponiamo che ad un certo punto buf.size() non sia uguale a 0 . JVM chiama notify()invece di notifyAll()e viene notificato il primo thread (non il secondo).

Il primo thread viene svegliato, verifica per buf.size()quale potrebbe tornare MAX_SIZEe torna in attesa. Il secondo thread non viene svegliato, continua ad attendere e non chiama get().


1

notify() sveglia il primo thread che ha chiamato wait() lo stesso oggetto.

notifyAll()riattiva tutti i thread che hanno richiamato wait()lo stesso oggetto.

Il thread con priorità più alta verrà eseguito per primo.


13
Nel caso notify()non sia esattamente " il primo thread ".
Bhesh Gurung,

6
non è possibile prevedere quale sarà scelto dalla VM. Solo Dio lo sa.
Sergii Shevchyk,

Non c'è garanzia chi sarà il primo (nessuna equità)
Ivan Voroshilin,

Risveglierà il primo thread solo se il sistema operativo lo garantisce, ed è probabile che non lo faccia. Si differenzia davvero per il sistema operativo (e il suo scheduler) per determinare quale thread riattivare.
Paul Stelian,

1

notifica notificherà solo un thread in stato di attesa, mentre notifica a tutti notificherà tutti i thread nello stato di attesa ora tutti i thread notificati e tutti i thread bloccati sono idonei per il blocco, fuori dal quale solo uno riceverà il blocco e tutti gli altri (compresi quelli che sono in stato di attesa prima) saranno in stato bloccato.


1

Riassumendo le eccellenti spiegazioni dettagliate sopra, e nel modo più semplice che mi viene in mente, ciò è dovuto alle limitazioni del monitor integrato JVM, che 1) viene acquisito sull'intera unità di sincronizzazione (blocco o oggetto) e 2) non discrimina la condizione specifica in attesa / notificata / circa.

Ciò significa che se vengono utilizzati più thread in attesa in condizioni diverse e viene utilizzato notification (), il thread selezionato potrebbe non essere quello che farebbe progressi sulla condizione appena soddisfatta, causando quel thread (e altri thread attualmente ancora in attesa che sarebbero in grado per soddisfare la condizione, ecc.) di non essere in grado di fare progressi, e infine la fame o il blocco del programma.

Al contrario, notifyAll () consente a tutti i thread in attesa di riacquistare il blocco e verificare le rispettive condizioni, consentendo in tal modo di compiere progressi.

Quindi Notify () può essere utilizzato in modo sicuro solo se è garantito un thread in attesa per consentire di fare progressi nel caso in cui venga selezionato, il che in generale è soddisfatto quando tutti i thread all'interno dello stesso monitor controllano solo la stessa condizione - una condizione abbastanza rara caso in applicazioni del mondo reale.


0

Quando si chiama wait () dell '"oggetto" (aspettandosi che venga acquisito il blocco dell'oggetto), intern questo rilascerà il blocco su quell'oggetto e aiuterà gli altri thread ad avere il blocco su questo "oggetto", in questo scenario ci sarà più di 1 thread in attesa di "risorsa / oggetto" (considerando che anche gli altri thread hanno emesso l'attesa sullo stesso oggetto sopra e lungo la strada ci sarà un thread che riempie la risorsa / oggetto e invoca notification / notificationAll).

Qui quando si emette la notifica dello stesso oggetto (dallo stesso / altro lato del processo / codice), questo rilascerà un singolo thread bloccato e in attesa (non tutti i thread in attesa - questo thread rilasciato verrà scelto da JVM Thread Scheduler e tutto il processo di acquisizione dei blocchi sull'oggetto è uguale al normale).

Se hai solo un thread che condividerà / funzionerà su questo oggetto, è ok usare il metodo notify () da solo nella tua implementazione wait-notification.

se ti trovi in ​​una situazione in cui più di un thread legge e scrive su risorse / oggetti in base alla tua logica aziendale, allora dovresti andare per notificationAll ()

ora sto guardando come esattamente jvm sta identificando e rompendo il thread di attesa quando emettiamo notification () su un oggetto ...


0

Mentre ci sono alcune risposte solide sopra, sono sorpreso dal numero di confusioni e incomprensioni che ho letto. Questo probabilmente dimostra l'idea che si dovrebbe usare java.util.concurrent il più possibile invece di provare a scrivere il proprio codice concorrente rotto. Torna alla domanda: per riassumere, la migliore pratica oggi è EVITARE di avvisare () in TUTTE le situazioni a causa del problema di sveglia perso. Chiunque non lo capisca non dovrebbe essere autorizzato a scrivere un codice di concorrenza mission-critical. Se sei preoccupato per il problema della pastorizia, un modo sicuro per svegliare un thread alla volta è: 1. Costruire una coda di attesa esplicita per i thread in attesa; 2. Far attendere a ciascuno dei thread nella coda il suo predecessore; 3. Al termine, ogni chiamata di thread deve essere avvisata (). Oppure puoi usare Java.util.concurrent. *,


nella mia esperienza, l'uso dell'attesa / notifica viene spesso utilizzato nei meccanismi di coda in cui un thread ( Runnableimplementazione) elabora il contenuto di una coda. La wait()viene quindi utilizzata se la coda è vuota. E notify()viene chiamato quando vengono aggiunte informazioni. -> in tal caso c'è solo 1 thread che chiama mai il wait(), quindi non sembra un po 'sciocco usare un notifyAll()se sai che c'è solo 1 thread in attesa.
bvdb,

-2

Svegliarsi tutto qui non ha molto significato. attendi notifica e notifica, tutti questi vengono inseriti dopo aver posseduto il monitor dell'oggetto. Se un thread è in fase di attesa e viene chiamato un messaggio di notifica, questo thread occuperà il blocco e nessun altro thread in quel punto potrà occuparlo. Quindi l'accesso simultaneo non può aver luogo affatto. Per quanto ne so, qualsiasi chiamata ad attendere di notifica e notifica può essere effettuata solo dopo aver preso il blocco sull'oggetto. Correggimi se sbaglio.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.