Domanda

Se si cerca su Google "differenza tra notify() E notifyAll()" appariranno molte spiegazioni (tralasciando i paragrafi javadoc).Tutto si riduce al numero di thread in attesa che vengono riattivati:uno in notify() e tutto compreso notifyAll().

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

Qual è il utile differenza fra notificare() E notificaTutti() Poi?Mi sto perdendo qualcosa?

È stato utile?

Soluzione

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

Questo non è corretto. o.notifyAll() si sveglia Tutto dei thread bloccati o.wait() chiamate.Ai thread è consentito solo il ritorno da o.wait() uno per uno, ma ciascuno Volere ottenere il loro 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 dirlo a tutti contemporaneamente?

In alcuni casi, tutti i thread in attesa possono intraprendere azioni utili una volta terminata l'attesa.Un esempio potrebbe essere un insieme di thread in attesa del completamento di una determinata attività;una volta terminata l'attività, tutti i thread in attesa possono continuare con la propria attività.In tal caso utilizzeresti notificaTutti() per riattivare tutti i thread in attesa contemporaneamente.

Un altro caso, ad esempio il blocco mutuamente 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 utilizzare notificare().Implementato correttamente, tu Potevo utilizzo notificaTutti() anche in questa situazione, ma riattiveresti inutilmente thread che comunque non possono 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 un o.notifyAll() call riattiva più di un thread in attesa e il primo che ritorna dal file o.wait() fa lasciare la condizione nello stato falso, quindi gli altri thread risvegliati torneranno in attesa.

Altri suggerimenti

Chiaramente, notify riattiva (qualsiasi) un thread nel set di attesa, notifyAll riattiva tutti i thread nel set di attesa.La discussione che segue dovrebbe chiarire ogni dubbio. notifyAll dovrebbe essere usato la maggior parte del tempo.Se non sei sicuro di quale utilizzare, utilizza notifyAll.Si prega di consultare la spiegazione che segue.

Leggi con molta attenzione e capisci.Per favore mandami un'e-mail se hai domande.

Guarda produttore/consumatore (si presuppone che sia una classe ProducerConsumer con due metodi).È ROTTO (perché usa notify) - sì, POTREBBE funzionare - anche la maggior parte delle volte, ma potrebbe anche causare un punto morto - 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 while loop nel caso in cui otteniamo questa situazione:

Il consumatore 1 (C1) entra nel blocco sincronizzato e il buffer è vuoto, quindi C1 viene messo nel set di attesa (tramite il wait chiamata).Il consumatore 2 (C2) sta per entrare nel metodo sincronizzato (nel 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 riacquisire il blocco dell'oggetto nel punto X (sopra).

Ora C1 e C2 stanno tentando di acquisire il blocco della sincronizzazione.Uno di essi (in modo non deterministico) viene scelto ed entra nel metodo, l'altro viene bloccato (non in attesa, ma bloccato, cercando di acquisire il blocco del metodo).Diciamo che C2 ottiene prima il blocco.C1 sta ancora bloccando (cercando di acquisire il lock su X).C2 completa il metodo e rilascia il blocco.Ora C1 acquisisce il blocco.Indovina un po', fortunatamente abbiamo a while loop, perché C1 esegue il controllo del loop (guardia) e gli viene impedito di rimuovere un elemento inesistente dal buffer (C2 l'ha già capito!).Se non avessimo un while, otterremmo un IndexArrayOutOfBoundsException poiché C1 tenta di rimuovere il primo elemento dal buffer!

ORA,

Ok, ora perché abbiamo bisogno di NotifyAll?

Nell'esempio produttore/consumatore sopra sembra che possiamo farla franca notify.Sembra così, perché possiamo dimostrare che le guardie sul Aspettare i circuiti di produttore e consumatore si escludono a vicenda.Cioè, sembra che non possiamo avere un thread in attesa nel file put metodo così come il get 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, DOBBIAMO usarlo notifyAll.Vediamo perché...

Supponiamo di avere un buffer di dimensione 1 (per rendere l'esempio facile da seguire).I passaggi seguenti ci portano ad un punto morto.Tieni presente che OGNI MOMENTO in cui un thread viene riattivato con notifica, può essere selezionato in modo non deterministico dalla JVM, ovvero qualsiasi thread in attesa può essere riattivato.Si noti inoltre che quando più thread bloccano l'accesso a un metodo (ad es.cercando di acquisire un blocco), l'ordine di acquisizione può essere non deterministico.Ricorda inoltre che un thread può trovarsi solo in uno dei metodi alla volta: i metodi sincronizzati consentono l'esecuzione di un solo thread (ad es.mantenendo il blocco di) qualsiasi metodo (sincronizzato) nella classe.Se si verifica la seguente sequenza di eventi, si verifica un deadlock:

PASSO 1:
- P1 inserisce 1 carattere nel buffer

PASSO 2:
- Tentativi P2 put - controlla il ciclo di attesa - è già un carattere - attende

PASSO 3:
- Tentativi P3 put - controlla il ciclo di attesa - è già un carattere - attende

PASSO 4:
- C1 tenta di ottenere 1 carattere
- C2 tenta di ottenere 1 blocco di caratteri all'ingresso del file get metodo
- C3 tenta di ottenere 1 blocco di caratteri all'ingresso del file get metodo

PASSO 5:
- C1 sta eseguendo il get metodo: ottiene il carattere, chiama notify, esce dal metodo
- IL notify sveglia P2
- MA, C2 entra nel metodo prima che P2 possa farlo (P2 deve riacquisire il blocco), quindi P2 blocca l'ingresso nel put metodo
- C2 controlla il ciclo di attesa, non ci sono più caratteri nel buffer, quindi attende
- C3 entra nel metodo dopo C2, ma prima di P2, controlla il ciclo di attesa, non ci sono più caratteri nel buffer, quindi attende

PASSO 6:
- ORA:ci sono P3, C2 e C3 in attesa!
- Alla fine P2 acquisisce il lock, inserisce un carattere nel buffer, chiama notify, esce dal metodo

PASSO 7:
- La notifica di P2 riattiva P3 (ricorda che qualsiasi thread può essere riattivato)
- P3 controlla la condizione del ciclo di attesa, c'è già un carattere nel buffer, quindi attende.
- NIENTE PIÙ THREAD DA CHIAMARE AVVISO e TRE THREAD PERMANENTEMENTE SOSPESI!

SOLUZIONE:Sostituire notify con notifyAll nel codice produttore/consumatore (sopra).

Differenze utili:

  • Utilizzo notificare() se tutti i thread in attesa sono intercambiabili (l'ordine in cui si attivano 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 modalità di sospensione.

  • Utilizzo notificaTutti() 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 attendono il completamento dell'operazione prima di accedere alla risorsa.

Penso che dipenda da come le risorse vengono prodotte e consumate.Se sono disponibili 5 oggetti di lavoro contemporaneamente e si hanno 5 oggetti consumer, avrebbe senso 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 svegliare tutti gli oggetti di consumo per gareggiare 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 () è generalmente utilizzato per 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ò affrontarlo.Il metodo Notifyall () è effettivamente utilizzato nella maggior parte degli altri casi.Rigorosamente, è necessario avvisare i camerieri di una condizione che potrebbe consentire a più camerieri di procedere.Ma questo è spesso difficile da sapere.Quindi come regola generale, Se non hai una logica particolare per l'utilizzo di Notify (), probabilmente dovresti usare NotifyAll (), perché è spesso difficile sapere esattamente quali thread aspetteranno un particolare oggetto e perché.

Tieni presente che con le utilità di concorrenza puoi anche scegliere tra signal() E signalAll() come vengono chiamati questi metodi lì.Quindi la domanda rimane valida anche con java.util.concurrent.

Doug Lea solleva un punto interessante nel suo discorso famoso libro:se un notify() E Thread.interrupt() verificarsi contemporaneamente, la notifica potrebbe effettivamente andare persa.Se questo può accadere e ha risvolti drammatici notifyAll() è una scelta più sicura anche se si paga il prezzo del sovraccarico (riattivando troppi thread per la maggior parte del tempo).

Da Joshua Bloch, lo stesso Java Guru in Effective Java 2a edizione:

"Articolo 69:Preferire le utilità di concorrenza per attendere e inviare notifiche".

Ecco un esempio.Eseguirlo.Quindi cambia uno dei notifyAll() in notify() e guarda 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 del consumatore

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 del produttore

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) { }
        }
    }
}

Breve riassunto:

Preferisco sempre notificaTutti() Sopra notificare() a meno che tu non abbia un'applicazione massivamente parallela in cui un gran numero di thread fanno tutti la stessa cosa.

Spiegazione:

notificare() ...] sveglia un singolo thread.Perché notificare() Non ti consente di specificare il thread che viene svegliato, è utile solo in applicazioni enormemente parallele, ovvero programmi con un gran numero di thread, tutti facendo cari simili.In un'applicazione del genere, non ti interessa quale thread viene svegliato.

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

Confrontare notificare() con notificaTutti() nella situazione sopra descritta:un'applicazione estremamente parallela in cui i thread fanno la stessa cosa.Se chiami notificaTutti() in quel caso, notificaTutti() indurrà il risveglio (cioè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 monitor per l'oggetto Aspettare(), notificare(), O notificaTutti() è stato chiamato), sprecando quindi risorse di calcolo.

Pertanto, se non disponi di un'applicazione in cui un numero enorme di thread fa la stessa cosa contemporaneamente, preferisci notificaTutti() Sopra notificare().Perché?Perché, come hanno già risposto altri utenti in questo forum, notificare()

riattiva un singolo thread in attesa sul monitor di questo oggetto....] la scelta è arbitrario 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 consumer-produttore in cui i consumatori sono pronti (ad es. Aspettare() ing) a consumare, i produttori sono pronti (cioè Aspettare() ing) da produrre e la coda degli articoli (da produrre/consumare) è vuota.In quel caso, notificare() potrebbero svegliarsi solo i consumatori e mai i produttori perché a scegliere chi svegliarsi è lui arbitrario.Il ciclo produttore-consumatore non farebbe alcun progresso sebbene produttori e consumatori siano pronti rispettivamente a produrre e consumare.Invece, un consumatore viene svegliato (ad es.lasciando il Aspettare() status), non toglie un elemento dalla coda perché è vuoto e notificare() C'è un altro consumatore che procede.

In contrasto, notificaTutti() risveglia sia i produttori che i consumatori.La scelta di chi programmare dipende dallo scheduler.Naturalmente, a seconda dell'implementazione dello scheduler, lo scheduler potrebbe anche schedulare solo i consumatori (ad es.se assegni ai thread consumer una priorità molto alta).Tuttavia, il presupposto qui è che il pericolo che lo scheduler pianifichi solo i consumatori sia inferiore al pericolo che la JVM risvegli solo i consumatori perché qualsiasi scheduler ragionevolmente implementato non fa solo arbitrario decisioni.Piuttosto, la maggior parte delle implementazioni dello scheduler fanno almeno qualche sforzo per prevenire la fame.

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

Fondamentalmente:

  1. se hai più thread in attesa con la 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 di 1.) e,
  4. la transizione dallo stato A allo stato B dovrebbe avvisare i thread in 1.

ALLORA dovresti usare notifyAll a meno che tu non abbia garanzie dimostrabili che i wakeup persi siano impossibili.

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

Si può facilmente scrivere un interleaving di operazioni in cui, partendo da una coda vuota, interagiscono 2 accodatori e 2 dequeuer e 1 accodatore rimarrà dormiente.

Questo è un problema probabilmente paragonabile al problema dello stallo.

Spero che questo possa fugare qualche dubbio.

notificare() :Il metodo Notify () sveglia un thread in attesa del blocco (il primo thread che chiamato wait () su quel blocco).

notificaTutti() :Il metodo notifyAll() sveglia tutti i thread in attesa del lock;JVM seleziona uno dei thread dall'elenco dei thread in attesa del blocco e si sveglia che si thread.

Nel caso di un singolo thread in attesa di un blocco, non c'è differenza significativa tra notify() e notifyAll().Tuttavia, quando c'è più di un thread in attesa del blocco, sia in notify() che in notifyAll(), l'esatto thread risvegliato è sotto il controllo della JVM e non puoi controllare a livello di codice il risveglio di un thread specifico.

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

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

notify() riattiverà un thread mentre notifyAll() sveglierà tutti.Per quanto ne so non esiste una via di mezzo.Ma se non sei sicuro di cosa notify() farà ai tuoi thread, usa notifyAll().Funziona come un incantesimo ogni volta.

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

Ecco una spiegazione più semplice:

Hai ragione nel dire che se usi notify() 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 arriva dopo.

Supponiamo che i thread A, B e C siano in attesa di questo oggetto e che il thread A ottenga il monitor.La differenza sta in ciò che accade una volta che A rilascia il monitor.Se hai utilizzato notify(), B e C sono ancora bloccati in wait():non aspettano sul monitor, aspettano di essere avvisati.Quando A rilascia il monitor, B e C saranno ancora seduti lì, in attesa di una notifica().

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

Esistono tre stati per un thread.

  1. WAIT: il thread non utilizza alcun ciclo della CPU
  2. BLOCCATO: il thread è bloccato durante il tentativo di acquisire un monitor. Potrebbe ancora utilizzare i cicli della CPU
  3. IN CORSO - Il thread è in esecuzione.

Ora, quando viene chiamato notify(), JVM seleziona un thread e lo sposta nello stato BLOCCATO e quindi nello stato RUNNING poiché non c'è concorrenza per l'oggetto monitor.

Quando viene chiamato notifyAll(), JVM seleziona tutti i thread e li sposta tutti nello stato BLOCCATO.Tutti questi thread otterranno il blocco dell'oggetto in base alla priorità. Il thread che è in grado di acquisire per primo il monitor potrà prima passare allo stato RUNNING e così via.

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

Perché utilizzare notifyAll, anche quando ogni prodotto è destinato a un singolo consumatore?

Consideriamo produttori e consumatori, semplificati come segue.

Produttore:

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

Consumatore:

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

Assumiamo 2 produttori e 2 consumatori, che condividano un buffer di dimensione 1.L'immagine seguente illustra uno scenario che porta a a situazione di stallo, cosa che verrebbe evitata se tutti i thread venissero utilizzati notificaTutto.

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

deadlock due to notify

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

Considera la seguente parte di codice eseguita 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 utilizzando 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 hai 1000 thread, verranno attivati ​​e valutati 999 thread dopo il primo notifyAll(), poi 998, poi 997 e così via.Al contrario, con il notify() soluzione, verrà risvegliato solo un thread.

Utilizzo notifyAll() quando devi 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 synchronized i blocchi che sono stati risvegliati verranno eseguiti in sequenza, non tutti in una volta.Diciamo che ci sono tre thread in attesa nell'esempio precedente e il quarto thread chiama notifyAll().Tutti e tre i thread verranno risvegliati ma solo uno inizierà l'esecuzione e controllerà la condizione del file while ciclo continuo.Se la condizione è true, chiamerà wait() di nuovo, e solo allora il secondo thread inizierà l'esecuzione e ne controllerà il file while condizione del ciclo e così via.

Preso da 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 capisco è (dal blog sopra menzionato, commento di "Yann TM" su risposta accettata e Giava documenti):

  • notificare() :JVM risveglia uno dei thread in attesa su questo oggetto.La selezione del thread viene effettuata arbitrariamente senza equità.Quindi lo stesso filo può essere risvegliato ancora e ancora.Quindi lo stato del sistema cambia ma non viene fatto alcun progresso reale.Creando così un livelock.
  • notificaTutto() :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 migliore della selezione da parte di JVM.Quindi, garantendo la vivacità.

Dai un'occhiata al codice pubblicato da @xagyg.

Supponiamo che due thread diversi stiano aspettando due condizioni diverse:
IL primo filo sta aspettando buf.size() != MAX_SIZE, e il secondo filo sta aspettando buf.size() != 0.

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

Il primo thread viene svegliato, controlla buf.size() che potrebbe ritornare MAX_SIZE, e torna ad aspettare.Il secondo thread non si sveglia, continua ad aspettare e non chiama get().

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

notifyAll() sveglia tutti i thread che hanno chiamato wait() sullo stesso oggetto.

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

Vorrei menzionare quanto spiegato in Java Concurrency in Practice:

Primo punto, se Notify o NotifyAll?

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

Se due thread A e B sono in attesa di diverse condizioni di condizione della stessa coda di condizione e viene chiamata la notifica, allora è fino a JVM a quale thread JVM avviserà.

Ora, se la notifica era pensata per il thread A e JVM Notified Thread B, il thread B si sveglierà e vedrà che questa notifica non è utile, quindi aspetterà di nuovo.E il thread A non verrà mai a conoscenza di questo segnale mancato e qualcuno ha dirottato la notifica.

Pertanto, Calling NotifyAll risolverà questo problema, ma ancora una volta avrà un impatto sulle prestazioni in quanto notificherà tutti i thread e tutti i thread competeranno per lo stesso blocco e implicherà l'interruttore di contesto e quindi il caricamento su CPU.Ma dovremmo preoccuparci delle prestazioni solo se si sta comportando correttamente, se il suo comportamento stesso non è corretto, le prestazioni non sono inutili.

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

notify notificherà solo un thread che è in stato di attesa, mentre notify all notificherà tutti i thread nello stato di attesa ora tutti i thread notificati e tutti i thread bloccati sono idonei per il blocco, di cui solo uno otterrà il blocco e tutti gli altri (compresi quelli che prima si trovavano nello stato di attesa) saranno nello stato bloccato.

notify() - Seleziona un thread casuale dal set di attesa dell'oggetto e lo inserisce nel file BLOCKED stato.Il resto dei thread nel set di attesa dell'oggetto sono ancora nel file WAITING stato.

notifyAll() - Sposta tutti i thread dal set di attesa dell'oggetto a BLOCKED stato.Dopo l'uso notifyAll(), non ci sono thread rimanenti nel set di attesa dell'oggetto condiviso perché ora sono tutti presenti BLOCKED stato e non dentro WAITING stato.

BLOCKED - bloccato per l'acquisizione del lucchetto.WAITING - in attesa di notifica (o bloccato per il completamento dell'iscrizione).

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

Ciò significa che se più thread sono in attesa di condizioni diverse e viene utilizzato notify(), 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 soddisfare la condizione, ecc.) non essere in grado di fare progressi ed eventualmente morire di fame o interrompere il programma.

Al contrario, notifyAll() consente a tutti i thread in attesa di riacquisire eventualmente il blocco e verificare la rispettiva condizione, consentendo così eventualmente di fare progressi.

Quindi notify() può essere utilizzato in modo sicuro solo se è garantito che qualsiasi thread in attesa consenta di procedere nel caso in cui venga selezionato, cosa che in generale è soddisfatta quando tutti i thread all'interno dello stesso monitor controllano solo una e la stessa condizione - una condizione abbastanza rara caso nelle applicazioni del mondo reale.

Quando chiami wait() dell '"oggetto" (aspettando che il blocco dell'oggetto venga acquisito), l'intern 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 della "risorsa/oggetto" (considerando che anche gli altri thread hanno emesso l'attesa sullo stesso oggetto sopra e in seguito ci sarà un thread che riempirà la risorsa/oggetto e invoca notify/notifyAll).

Qui quando emetti 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à selezionato dal thread JVM Lo scheduler e tutto il processo di acquisizione del blocco sull'oggetto sono gli stessi del normale).

Se hai un solo thread che condividerà/lavorerà su questo oggetto, è possibile utilizzare solo il metodo notify() nell'implementazione wait-notify.

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 scegliere notifyAll()

ora sto osservando come esattamente la jvm identifica e interrompe il thread in attesa quando emettiamo notify() su un oggetto ...

Anche se sopra ci sono alcune risposte valide, sono sorpreso dal numero di confusioni e incomprensioni che ho letto.Ciò probabilmente dimostra l'idea che si dovrebbe usare java.util.concurrent il più possibile invece di provare a scrivere il proprio codice simultaneo danneggiato.Torniamo alla domanda:per riassumere, la migliore pratica oggi è EVITARE notify() in TUTTE le situazioni a causa del problema del risveglio mancato.Chiunque non lo capisca non dovrebbe essere autorizzato a scrivere codice di concorrenza mission-critical.Se sei preoccupato per il problema della pastorizia, un modo sicuro per risvegliare un thread alla volta è:1.Crea una coda di attesa esplicita per i thread in attesa;2.Chiedi a ciascun thread in coda di attendere il suo predecessore;3.Chiedi a ogni thread di chiamare notifyAll() al termine.Oppure puoi utilizzare Java.util.concurrent.*, che lo ha già implementato.

Svegliarsi non ha molto significato qui.wait notify e notifyall, tutti questi vengono inseriti dopo aver posseduto il monitor dell'oggetto.Se un thread è in fase di attesa e viene chiamata la notifica, questo thread prenderà il blocco e nessun altro thread a quel punto potrà prendere quel blocco.Pertanto l'accesso simultaneo non può assolutamente avere luogo.Per quanto ne so, qualsiasi chiamata di attesa notifica e notifica tutto può essere effettuata solo dopo aver bloccato l'oggetto.Correggimi se sbaglio.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top