Ein einfaches Szenario mit wait () und notify () in Java

181

Kann ich ein komplettes einfaches Szenario erhalten, dh ein Tutorial, das vorschlägt, wie dies verwendet werden soll, insbesondere mit einer Warteschlange?

Olaseni
quelle

Antworten:

269

Die Methoden wait()und notify()sollen einen Mechanismus bereitstellen, mit dem ein Thread blockieren kann, bis eine bestimmte Bedingung erfüllt ist. Ich gehe davon aus, dass Sie eine Implementierung für eine blockierende Warteschlange schreiben möchten, in der Sie einen Backing-Store mit Elementen fester Größe haben.

Das erste, was Sie tun müssen, ist, die Bedingungen zu identifizieren, auf die die Methoden warten sollen. In diesem Fall soll die put()Methode blockiert werden, bis freier Speicherplatz im Speicher vorhanden ist, und die take()Methode soll blockiert werden, bis ein Element zurückgegeben werden muss.

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;
    }
}

Es gibt einige Dinge zu beachten, wie Sie die Warte- und Benachrichtigungsmechanismen verwenden müssen.

Zunächst müssen Sie sicherstellen, dass alle Aufrufe an wait()oder notify()innerhalb eines synchronisierten Codebereichs (wobei die wait()und notify()-Aufrufe für dasselbe Objekt synchronisiert werden). Der Grund dafür (abgesehen von den Standardbedenken hinsichtlich der Thread-Sicherheit) liegt in einem sogenannten Fehlensignal.

Ein Beispiel hierfür ist, dass ein Thread möglicherweise aufruft, put()wenn die Warteschlange voll ist. Er überprüft dann die Bedingung und stellt fest, dass die Warteschlange voll ist. Bevor er jedoch einen anderen Thread blockieren kann, ist geplant. Dieser zweite Thread ist dann take()ein Element aus der Warteschlange und benachrichtigt die wartenden Threads, dass die Warteschlange nicht mehr voll ist. Da der erste Thread die Bedingung jedoch bereits überprüft hat, wird er wait()nach dem erneuten Planen einfach aufgerufen , obwohl er Fortschritte erzielen könnte.

Durch die Synchronisierung auf einem freigegebenen Objekt können Sie sicherstellen, dass dieses Problem nicht auftritt, da der take()Aufruf des zweiten Threads erst dann Fortschritte erzielen kann, wenn der erste Thread tatsächlich blockiert wurde.

Zweitens müssen Sie die Bedingung, die Sie überprüfen, in eine while-Schleife und nicht in eine if-Anweisung einfügen, da dies als falsches Aufwecken bezeichnet wird. Hier kann ein wartender Thread manchmal wieder aktiviert werden, ohne notify()aufgerufen zu werden. Wenn Sie diese Prüfung in eine while-Schleife einfügen, wird sichergestellt, dass bei einem falschen Aufwecken die Bedingung erneut überprüft wird und der Thread wait()erneut aufgerufen wird.


Wie einige der anderen Antworten bereits erwähnt haben, hat Java 1.5 eine neue Parallelitätsbibliothek (im java.util.concurrentPaket) eingeführt, die eine übergeordnete Abstraktion über den Warte- / Benachrichtigungsmechanismus bietet. Mit diesen neuen Funktionen können Sie das ursprüngliche Beispiel folgendermaßen umschreiben:

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();
        }
    }
}

Wenn Sie tatsächlich eine Blockierungswarteschlange benötigen, sollten Sie natürlich eine Implementierung der BlockingQueue- Schnittstelle verwenden.

Für solche Dinge würde ich Java Concurrency in der Praxis sehr empfehlen , da es alles abdeckt, was Sie über Probleme und Lösungen im Zusammenhang mit Parallelität wissen möchten.

Jared Russell
quelle
7
@greuze, notifyweckt nur einen Thread. Wenn zwei Consumer-Threads miteinander konkurrieren, um ein Element zu entfernen, weckt eine Benachrichtigung möglicherweise den anderen Consumer-Thread, der nichts dagegen tun kann und wieder in den Ruhezustand wechselt (anstelle des Produzenten, von dem wir gehofft hatten, dass er ein neues Element einfügt.) Weil Der Producer-Thread wird nicht geweckt, es wird nichts eingefügt und jetzt werden alle drei Threads auf unbestimmte Zeit schlafen. Ich entfernte meinen vorherigen Kommentar, als er (fälschlicherweise) sagte, dass falsches Aufwachen die Ursache des Problems war (ist es nicht.)
finnw
1
@finnw Soweit ich das beurteilen kann, kann das von Ihnen entdeckte Problem mit notifyAll () gelöst werden. Habe ich recht?
Clint Eastwood
1
Das hier von @Jared gegebene Beispiel ist ziemlich gut, hat aber einen ernsthaften Rückgang. Im Code wurden alle Methoden als synchronisiert markiert, aber es können NICHT ZWEI SYNCHRONISIERTE METHODEN gleichzeitig ausgeführt werden. Wie kommt es dann, dass es einen zweiten Thread im Bild gibt?
Shivam Aggarwal
10
@ Brut3Forc3 Sie müssen das Javadoc von wait () lesen: Es heißt: Der Thread gibt den Besitz dieses Monitors frei . Sobald wait () aufgerufen wird, wird der Monitor freigegeben und ein anderer Thread kann eine andere synchronisierte Methode der Warteschlange ausführen.
JB Nizet
1
@JBNizet. "Ein Beispiel hierfür ist, dass ein Thread put () aufrufen kann, wenn die Warteschlange voll ist. Er überprüft dann die Bedingung und stellt fest, dass die Warteschlange voll ist. Bevor jedoch ein anderer Thread blockiert werden kann, ist dies geplant." Der zweite Thread ist geplant, wenn noch keine Wartezeit aufgerufen wurde.
Shivam Aggarwal
148

Kein Warteschlangenbeispiel, aber extrem einfach :)

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();
        }
    }
}

Einige wichtige Punkte:
1) NIEMALS tun

 if(!pizzaArrived){
     wait();
 }

Immer verwenden während (Bedingung), weil

  • a) Threads können sporadisch aus dem Wartezustand erwachen, ohne von jemandem benachrichtigt zu werden. (Selbst wenn der Pizzabote nicht klingelte, beschloss jemand, die Pizza zu essen.)
  • b) Sie sollten den Zustand nach dem Erwerb der synchronisierten Sperre erneut überprüfen. Nehmen wir an, Pizza hält nicht ewig. Du bist wach, stell dich für die Pizza an, aber es ist nicht genug für alle. Wenn Sie nicht überprüfen, können Sie Papier essen! :) (wahrscheinlich besseres Beispiel wäre while(!pizzaExists){ wait(); }.

2) Sie müssen die Sperre (synchronisiert) gedrückt halten, bevor Sie wait / nofity aufrufen. Die Fäden müssen vor dem Aufwachen ebenfalls blockiert werden.

3) Vermeiden Sie es, eine Sperre innerhalb Ihres synchronisierten Blocks zu erlangen, und versuchen Sie, keine außerirdischen Methoden aufzurufen (Methoden, die Sie nicht genau kennen). Wenn Sie müssen, stellen Sie sicher, dass Sie Maßnahmen ergreifen, um Deadlocks zu vermeiden.

4) Seien Sie vorsichtig mit notify (). Bleiben Sie bei notifyAll (), bis Sie wissen, was Sie tun.

5) Lesen Sie zu guter Letzt Java Concurrency in der Praxis !

Enno Shioji
quelle
1
Könnten Sie bitte erläutern, warum Sie nicht "if (! PizzaArrived) {wait ();}" verwenden sollten?
Jeder
2
@ Jeder: Einige Erklärungen hinzugefügt. HTH.
Enno Shioji
1
Warum die pizzaArrivedFlagge benutzen ? Wenn das Flag ohne Aufruf geändert notifywird, hat dies keine Auswirkung. Auch nur mit waitund notifyruft das Beispiel auf.
Pablo Fernandez
2
Ich verstehe nicht - Thread 1 führt die eatPizza () -Methode aus, gibt den obersten synchronisierten Block ein und synchronisiert die MyHouse-Klasse. Es ist noch keine Pizza angekommen und wartet nur. Jetzt versucht Thread 2, die Pizza durch Aufrufen der pizzaGuy () -Methode zu liefern. kann aber nicht, da Thread 1 das Schloss bereits besitzt und es nicht aufgibt (es wartet ständig). Tatsächlich ist das Ergebnis ein Deadlock - Thread 1 wartet darauf, dass Thread 2 die notifyAll () -Methode ausführt, während Thread 2 darauf wartet, dass Thread 1 die Sperre für die MyHouse-Klasse aufgibt ... Was fehlt mir? Hier?
Flamming_Python
1
Nein, wenn eine Variable durch ein synchronizedSchlüsselwort geschützt ist , ist es redundant, die Variable zu deklarieren volatile, und es wird empfohlen, sie zu vermeiden, um Verwirrung zu vermeiden. @Mrida
Enno Shioji
37

Auch wenn Sie nach wait()und notify()speziell gefragt haben , halte ich dieses Zitat für wichtig genug:

Josh Bloch, Effective Java 2nd Edition , Punkt 69: Bevorzugen Sie Parallelitätsdienstprogramme gegenüber waitund notify(Hervorhebung seiner):

Angesichts der Schwierigkeit der Verwendung waitund notifykorrekten Verwendung sollten Sie stattdessen die übergeordneten Parallelitätsdienstprogramme verwenden, die [...] verwenden waitund notifydirekt der Programmierung in der "Parallelitätsassemblersprache" entsprechen, im Vergleich zu der übergeordneten Sprache, die von bereitgestellt wird java.util.concurrent. Es ist selten, wenn überhaupt, Grund zu der Verwendung waitund notifyin neuem Code .

Polygenschmierstoffe
quelle
Die im Paket java.util.concurrent bereitgestellten BlockingQueueS sind nicht persistent. Was können wir verwenden, wenn die Warteschlange dauerhaft sein muss? dh wenn das System mit 20 Elementen in der Warteschlange ausfällt, müssen diese beim Neustart des Systems vorhanden sein. Da die Warteschlangen von java.util.concurrent alle nur im Speicher zu sein scheinen, gibt es eine Möglichkeit, diese so zu verwenden, wie sie / gehackt / überschrieben sind, um Implementierungen bereitzustellen, die persistenzfähig sind?
Volksman
1
Vielleicht könnte die Sicherungswarteschlange bereitgestellt werden? Das heißt, wir würden eine permanente Implementierung der Warteschlangenschnittstelle bereitstellen.
Volksman
Dies ist sehr gut in diesem Zusammenhang zu erwähnen, dass Sie das notify()und das nie wait()wieder
verwenden müssten
7

Haben Sie sich dieses Java-Tutorial angesehen ?

Außerdem würde ich Ihnen raten, sich nicht mit solchen Dingen in echter Software zu beschäftigen. Es ist gut, damit zu spielen, damit Sie wissen, was es ist, aber Parallelität hat überall Fallstricke. Es ist besser, übergeordnete Abstraktionen und synchronisierte Sammlungen oder JMS-Warteschlangen zu verwenden, wenn Sie Software für andere Personen erstellen.

Das ist zumindest was ich tue. Ich bin kein Experte für Parallelität, daher halte ich mich nach Möglichkeit davon fern, Threads von Hand zu behandeln.

Extraneon
quelle
2

Beispiel

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
     }

}
Ferhat KOÇER
quelle
0

Beispiel für wait () und notifyall () in Threading.

Eine synchronisierte statische Array-Liste wird als Ressource verwendet, und die wait () -Methode wird aufgerufen, wenn die Array-Liste leer ist. Die notify () -Methode wird aufgerufen, sobald ein Element für die Array-Liste hinzugefügt wird.

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());
   }
}
Srinivas
quelle
1
Bitte überprüfen Sie diese Zeile if(arrayList.size() == 0), ich denke, es könnte ein Fehler hier sein.
Wizmann