web-dev-qa-db-ja.com

javaでwait()およびnotify()を使用する簡単なシナリオ

これをどのように使用するか、具体的にはキューで使用することを提案する完全な単純なシナリオ、つまりチュートリアルを入手できますか?

169
Olaseni

wait()およびnotify()メソッドは、特定の条件が満たされるまでスレッドをブロックできるメカニズムを提供するように設計されています。このため、要素の固定サイズのバッキングストアがあるブロッキングキューの実装を書きたいと思っています。

最初に行う必要があるのは、メソッドが待機する条件を識別することです。この場合、ストアに空き領域ができるまでput()メソッドをブロックし、返される要素が見つかるまでtake()メソッドをブロックします。

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

待機および通知メカニズムを使用する必要がある方法について、注意すべきことがいくつかあります。

まず、wait()またはnotify()の呼び出しが、コードの同期領域内にあることを確認する必要があります(wait()およびnotify()呼び出しは同じオブジェクトで同期されます)。この理由は(標準のスレッド安全性の問題を除く)、シグナルの欠落と呼ばれるものが原因です。

この例は、キューがいっぱいになったときにスレッドがput()を呼び出してから、状態をチェックし、キューがいっぱいであることを確認しますが、別のスレッドをブロックする前にスケジュールします。この2番目のスレッドは、take()がキューからの要素であり、キューが満杯ではなくなったことを待機スレッドに通知します。ただし、最初のスレッドはすでに条件をチェックしているため、進捗があったとしても、再スケジュール後にwait()を呼び出すだけです。

共有オブジェクトで同期することにより、2番目のスレッドのtake()呼び出しが最初のスレッドが実際にブロックされるまで進行できないため、この問題が発生しないことを確認できます。

第二に、スプリアスウェイクアップと呼ばれる問題のため、ifステートメントではなく、whileループにチェックする条件を配置する必要があります。これは、notify()を呼び出さずに待機スレッドを再アクティブ化できる場合があります。 whileループにこのチェックを入れると、スプリアスウェイクアップが発生した場合、条件が再チェックされ、スレッドは_wait()を再度呼び出します。


他の回答のいくつかが言及したように、Java 1.5は、待機/通知メカニズムよりも高いレベルの抽象化を提供するように設計された新しい同時実行ライブラリ(Java.util.concurrentパッケージ内)を導入しました。これらの新機能を使用して、元の例を次のように書き換えることができます。

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

もちろん、実際にブロッキングキューが必要な場合は、 BlockingQueue インターフェイスの実装を使用する必要があります。

また、このようなものについては Javaの並行性の実践 を強くお勧めします。並行性に関連する問題と解決策について知りたいことはすべて網羅されているからです。

261
Jared Russell

キューの例ではありませんが、非常に簡単です:)

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

重要なポイント:
1)絶対にしない

 if(!pizzaArrived){
     wait();
 }

常にwhile(条件)を使用してください。

  • a)スレッドは、誰からも通知されることなく、散発的に待機状態から目覚めることができます。 (ピザの男がチャイムを鳴らしていなくても、誰かがピザを食べてみることにします。).
  • b)同期ロックを取得した後、状態を再度確認する必要があります。ピザが永遠に続かないとしましょう。あなたは目を覚まし、ピザのラインナップをしますが、それは皆にとって十分ではありません。チェックしなければ、紙を食べるかもしれません! :)(おそらくもっと良い例はwhile(!pizzaExists){ wait(); }です。

2)wait/nofityを呼び出す前に、ロックを保持(同期)する必要があります。スレッドは、起動する前にロックを取得する必要もあります。

3)同期ブロック内でロックを取得しないようにし、エイリアンメソッド(確実に何をしているかわからないメソッド)を呼び出さないようにします。必要な場合は、デッドロックを回避するための対策を講じてください。

4)notify()に注意してください。何をしているのかがわかるまで、notifyAll()を使い続けてください。

5)最後に、しかし大事なことを読んで、 Java並行性の実践

145
Enno Shioji

wait()notify()を具体的に要求したとしても、この引用はまだ十分に重要だと思います。

Josh Bloch、Effective Java 2nd Edition、Item 69:同時実行ユーティリティをwaitおよびnotifyより優先(強調):

waitnotifyを正しく使用することの難しさを考えると、代わりに高レベルの同時実行ユーティリティを使用する必要があります [...] waitnotifyを直接使用することは、 Java.util.concurrentによって提供される高レベル言語。 新しいコードでwaitnotifyを使用する理由はほとんどありません

34

これをご覧になりましたか Javaチュートリアル

さらに、この種のソフトウェアを実際のソフトウェアでプレイすることは避けてください。それで遊ぶのは良いので、それが何であるかを知っていますが、並行性にはあちこちに落とし穴があります。他の人のためのソフトウェアを構築している場合は、より高いレベルの抽象化と同期されたコレクションまたはJMSキューを使用することをお勧めします。

それは少なくとも私がしていることです。私は並行処理の専門家ではないので、可能な限り手動でスレッドを処理することは避けます。

6
extraneon

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
     }

}
2
Ferhat KOÇER

スレッドのwait()およびnotifyall()の例。

同期された静的配列リストがリソースとして使用され、配列リストが空の場合にwait()メソッドが呼び出されます。配列リストに要素が追加されると、notify()メソッドが呼び出されます。

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