Uno scenario semplice che utilizza wait () e notify () in Java


181

Posso ottenere uno scenario semplice completo, ad esempio un tutorial che suggerisce come utilizzare questo, in particolare con una coda?

Risposte:


269

I metodi wait()e notify()sono progettati per fornire un meccanismo per consentire a un thread di bloccarsi fino a quando non viene soddisfatta una condizione specifica. Per questo suppongo che tu voglia scrivere un'implementazione della coda di blocco, in cui hai un back-store di elementi di dimensioni fisse.

La prima cosa che devi fare è identificare le condizioni che desideri che i metodi aspettino. In questo caso, vorrai che il put()metodo si blocchi fino a quando non c'è spazio libero nell'archivio e vorrai che il take()metodo si blocchi fino a quando non ci sarà qualche elemento da restituire.

public class BlockingQueue<T> {

    private Queue<T> queue = new LinkedList<T>();
    private int capacity;

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    public synchronized void put(T element) throws InterruptedException {
        while(queue.size() == capacity) {
            wait();
        }

        queue.add(element);
        notify(); // notifyAll() for multiple producer/consumer threads
    }

    public synchronized T take() throws InterruptedException {
        while(queue.isEmpty()) {
            wait();
        }

        T item = queue.remove();
        notify(); // notifyAll() for multiple producer/consumer threads
        return item;
    }
}

Vi sono alcune cose da notare sul modo in cui è necessario utilizzare i meccanismi di attesa e notifica.

In primo luogo, è necessario assicurarsi che tutte le chiamate verso wait()o notify()siano all'interno di un'area di codice sincronizzata (con le chiamate wait()e notify()sincronizzate sullo stesso oggetto). La ragione di ciò (oltre alle preoccupazioni sulla sicurezza del thread standard) è dovuta a qualcosa noto come segnale perso.

Un esempio di ciò è che un thread può chiamare put()quando la coda sembra essere piena, quindi controlla la condizione, vede che la coda è piena, tuttavia prima che possa bloccare è programmato un altro thread. Questo secondo thread è quindi take()un elemento della coda e notifica ai thread in attesa che la coda non è più piena. Poiché il primo thread ha già verificato la condizione, chiamerà semplicemente wait()dopo essere stato riprogrammato, anche se potrebbe fare progressi.

Sincronizzando un oggetto condiviso, è possibile assicurarsi che questo problema non si verifichi, poiché la take()chiamata del secondo thread non sarà in grado di avanzare fino a quando il primo thread non si sarà effettivamente bloccato.

In secondo luogo, è necessario inserire la condizione che si sta verificando in un ciclo while, piuttosto che un'istruzione if, a causa di un problema noto come risvegli spuri. È qui che a volte un thread in attesa può essere riattivato senza notify()essere chiamato. Mettendo questo controllo in un ciclo while si assicurerà che se si verifica un risveglio spuria, la condizione verrà ricontrollata e il thread chiamerà di wait()nuovo.


Come alcune delle altre risposte menzionate, Java 1.5 ha introdotto una nuova libreria di concorrenza (nel java.util.concurrentpacchetto) progettata per fornire un'astrazione di livello superiore rispetto al meccanismo di attesa / notifica. Utilizzando queste nuove funzionalità, è possibile riscrivere l'esempio originale in questo modo:

public class BlockingQueue<T> {

    private Queue<T> queue = new LinkedList<T>();
    private int capacity;
    private Lock lock = new ReentrantLock();
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    public void put(T element) throws InterruptedException {
        lock.lock();
        try {
            while(queue.size() == capacity) {
                notFull.await();
            }

            queue.add(element);
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public T take() throws InterruptedException {
        lock.lock();
        try {
            while(queue.isEmpty()) {
                notEmpty.await();
            }

            T item = queue.remove();
            notFull.signal();
            return item;
        } finally {
            lock.unlock();
        }
    }
}

Naturalmente se in realtà è necessaria una coda di blocco, è necessario utilizzare un'implementazione dell'interfaccia BlockingQueue .

Inoltre, per cose come questa, consiglio vivamente la concorrenza Java in pratica , poiché copre tutto ciò che potresti voler sapere su problemi e soluzioni relativi alla concorrenza.


7
@greuze, notifysveglia solo un thread. Se due thread di consumatori sono in competizione per rimuovere un elemento, uno di notifica potrebbe riattivare l'altro thread di consumatore, che non può farci nulla e tornerà a dormire (invece del produttore, che speravamo potesse inserire un nuovo elemento.) Perché il thread del produttore non viene svegliato, non viene inserito nulla e ora tutti e tre i thread dormiranno indefinitamente. Ho rimosso il mio commento precedente in quanto diceva (a torto) che il risveglio spurio era la causa del problema (non lo è.)
finnw,

1
@finnw Per quanto ne so, il problema che hai individuato può essere risolto usando notifyAll (). Ho ragione?
Clint Eastwood,

1
L'esempio fornito qui da @Jared è piuttosto buono ma ha una caduta grave. Nel codice tutti i metodi sono stati contrassegnati come sincronizzati, ma NON POSSONO ESSERE ESEGUITI DUE METODI SINCRONIZZATI ALLO STESSO TEMPO, quindi come mai c'è un secondo thread nell'immagine.
Shivam Aggarwal,

10
@ Brut3Forc3 devi leggere il javadoc di wait (): dice: il thread rilascia la proprietà di questo monitor . Quindi, non appena viene chiamato wait (), il monitor viene rilasciato e un altro thread può eseguire un altro metodo sincronizzato della coda.
JB Nizet,

1
@JBNizet. "Un esempio di questo, è che un thread può chiamare put () quando la coda sembra essere piena, quindi controlla la condizione, vede che la coda è piena, tuttavia prima che possa bloccare un altro thread è programmato". Ecco come mai il secondo thread è programmato se l'attesa non è stata ancora chiamata
Shivam Aggarwal

148

Non un esempio di coda, ma estremamente semplice :)

class MyHouse {
    private boolean pizzaArrived = false;

    public void eatPizza(){
        synchronized(this){
            while(!pizzaArrived){
                wait();
            }
        }
        System.out.println("yumyum..");
    }

    public void pizzaGuy(){
        synchronized(this){
             this.pizzaArrived = true;
             notifyAll();
        }
    }
}

Alcuni punti importanti:
1) MAI farlo

 if(!pizzaArrived){
     wait();
 }

Utilizzare sempre while (condizione), perché

  • a) i thread possono sporadicamente svegliarsi dallo stato di attesa senza essere avvisati da nessuno. (Anche quando il ragazzo della pizza non suonava il campanello, qualcuno avrebbe deciso di provare a mangiare la pizza.).
  • b) È necessario verificare nuovamente la condizione dopo aver acquisito il blocco sincronizzato. Diciamo che la pizza non dura per sempre. Ti svegli, schierati per la pizza, ma non è abbastanza per tutti. Se non controlli, potresti mangiare carta! :) (probabilmente un esempio migliore sarebbe while(!pizzaExists){ wait(); }.

2) È necessario tenere il blocco (sincronizzato) prima di invocare wait / nofity. I thread devono anche acquisire il blocco prima di svegliarsi.

3) Cerca di evitare di acquisire alcun blocco all'interno del tuo blocco sincronizzato e cerca di non invocare metodi alieni (metodi che non conosci con certezza cosa stanno facendo). Se è necessario, assicurarsi di adottare misure per evitare deadlock.

4) Prestare attenzione con notify (). Rimani con NotifyAll () fino a quando non sai cosa stai facendo.

5) Ultimo, ma non meno importante, leggi Java Concurrency in Practice !


1
Potresti spiegare perché non usare "if (! PizzaArrived) {wait ();}"?
Tutti il

2
@Tutti: aggiunta qualche spiegazione. HTH.
Enno Shioji,

1
perché usare la pizzaArrivedbandiera? se il flag viene cambiato senza una chiamata notify, non avrà alcun effetto. Anche solo con waite notifychiama l'esempio funziona.
Pablo Fernandez,

2
Non capisco: il thread 1 esegue il metodo eatPizza (), inserisce il blocco sincronizzato superiore e si sincronizza sulla classe MyHouse. Nessuna pizza è ancora arrivata, quindi aspetta. Ora il thread 2 prova a consegnare la pizza chiamando il metodo pizzaGuy (); ma non può, poiché il thread 1 possiede già il blocco e non si sta arrendendo (è perennemente in attesa). In effetti il ​​risultato è un deadlock: il thread 1 è in attesa del thread 2 per eseguire il metodo notifyAll (), mentre il thread 2 è in attesa del thread 1 per rinunciare al blocco sulla classe MyHouse ... Cosa mi manca? Qui?
flamming_python

1
No, quando una variabile è protetta da una synchronizedparola chiave, è ridondante dichiararla volatilee si consiglia di evitarla per evitare confusione @mrida
Enno Shioji

37

Anche se hai chiesto wait()e in notify()particolare, ritengo che questa citazione sia ancora abbastanza importante:

Josh Bloch, Effective Java 2nd Edition , Articolo 69: Preferisco utilità concorrenza per waite notify(enfasi la sua):

Data la difficoltà di utilizzare waite notifycorrettamente, è necessario utilizzare le utilità di concorrenza di livello superiore invece [...] utilizzando waite notifydirettamente è come programmare in "linguaggio assembly di concorrenza", rispetto al linguaggio di livello superiore fornito da java.util.concurrent. C'è raramente, se mai, motivo per usare waite notifynel nuovo codice .


I BlockingQueueS forniti nel pacchetto java.util.concurrent non sono persistenti. Cosa possiamo usare quando la coda deve essere persistente? cioè se il sistema scende con 20 elementi nella coda ho bisogno che siano presenti al riavvio del sistema. Dato che tutte le code java.util.concurrent sembrano essere solo "in memoria", esiste un modo in cui queste potrebbero essere utilizzate come / hackerate / sovrascritte per fornire implementazioni che sono in grado di perseverare?
Volksman,

1
Forse potrebbe essere fornita la coda di supporto? ovvero forniremo un'implementazione dell'interfaccia della coda persistente.
Volksman,

È molto utile ricordare in questo contesto che non avresti mai più bisogno di usare il notify()e wait()ancora una volta
Chaklader Asfak Arefe,

7

Hai dato un'occhiata a questo tutorial Java ?

Inoltre, ti consiglierei di stare lontana dal giocare con questo tipo di cose con un vero software. È bello giocarci in modo da sapere di cosa si tratta, ma la concorrenza ha insidie ​​dappertutto. È meglio utilizzare astrazioni di livello superiore e raccolte sincronizzate o code JMS se si sta creando software per altre persone.

Questo è almeno quello che faccio. Non sono un esperto di concorrenza, quindi sto lontano dalla gestione dei thread a mano, ove possibile.


2

Esempio

public class myThread extends Thread{
     @override
     public void run(){
        while(true){
           threadCondWait();// Circle waiting...
           //bla bla bla bla
        }
     }
     public synchronized void threadCondWait(){
        while(myCondition){
           wait();//Comminucate with notify()
        }
     }

}
public class myAnotherThread extends Thread{
     @override
     public void run(){
        //Bla Bla bla
        notify();//Trigger wait() Next Step
     }

}

0

Esempio di wait () e notifyall () in Threading.

Un elenco di array statico sincronizzato viene utilizzato come metodo resource e wait () viene chiamato se l'elenco di array è vuoto. Il metodo notify () viene richiamato una volta aggiunto un elemento per l'elenco di array.

public class PrinterResource extends Thread{

//resource
public static List<String> arrayList = new ArrayList<String>();

public void addElement(String a){
    //System.out.println("Add element method "+this.getName());
    synchronized (arrayList) {
        arrayList.add(a);
        arrayList.notifyAll();
    }
}

public void removeElement(){
    //System.out.println("Remove element method  "+this.getName());
    synchronized (arrayList) {
        if(arrayList.size() == 0){
            try {
                arrayList.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }else{
            arrayList.remove(0);
        }
    }
}

public void run(){
    System.out.println("Thread name -- "+this.getName());
    if(!this.getName().equalsIgnoreCase("p4")){
        this.removeElement();
    }
    this.addElement("threads");

}

public static void main(String[] args) {
    PrinterResource p1 = new PrinterResource();
    p1.setName("p1");
    p1.start();

    PrinterResource p2 = new PrinterResource();
    p2.setName("p2");
    p2.start();


    PrinterResource p3 = new PrinterResource();
    p3.setName("p3");
    p3.start();


    PrinterResource p4 = new PrinterResource();
    p4.setName("p4");
    p4.start();     

    try{
        p1.join();
        p2.join();
        p3.join();
        p4.join();
    }catch(InterruptedException e){
        e.printStackTrace();
    }
    System.out.println("Final size of arraylist  "+arrayList.size());
   }
}

1
per favore ricontrolla questa linea if(arrayList.size() == 0), penso che potrebbe essere un errore qui.
Wizmann,
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.