Javaでwait()とnotify()を使用する簡単なシナリオ


181

特にキューでこれをどのように使用すべきかを示唆する完全な単純なシナリオ、つまりチュートリアルを入手できますか?

回答:


269

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()進行する可能性があっても、再スケジュールされた後に単に呼び出されます。

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

次に、偽のウェイクアップと呼ばれる問題のため、チェックする条件を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並行性の実践を強くお勧めします。これは、並行性に関連する問題と解決策について知りたいすべてのものをカバーしているためです。


7
@ greuze、notify1つのスレッドのみを起こします。2つのコンシューマスレッドが要素の削除を競合している場合、1つの通知が他のコンシューマスレッドを呼び起こす可能性があり、それはそれについて何もできず、スリープ状態に戻ります(プロデューサではなく、新しい要素を挿入することを期待していました)。プロデューサースレッドは起こされず、何も挿入されず、3つのスレッドすべてが無期限にスリープします。それは見せかけの起動が(。それはありません)問題の原因だったことを(誤って)言ったように私は私の以前のコメントを削除
finnw

1
@finnw私が知る限り、通知した問題はnotifyAll()を使用して解決できます。私は正しいですか?
クリントイーストウッド

1
ここで@Jaredによって与えられた例はかなり良いですが、深刻な落ち込みがあります。コードでは、すべてのメソッドが同期されているとマークされていますが、同時に2つの同期メソッドを実行することはできません。
Shivam Aggarwal

10
@ Brut3Forc3あなたはwait()のjavadocを読む必要があります:それは言う:スレッドはこのモニターの所有権を解放します。そのため、wait()が呼び出されるとすぐにモニターが解放され、別のスレッドがキューの別の同期されたメソッドを実行できます。
JBニゼット2015

1
@JBNizet。「この例としては、キューがいっぱいになったときにスレッドがput()を呼び出し、その後、状態をチェックし、キューがいっぱいであることを確認しますが、別のスレッドがブロックされる前にスケジュールされます。」待機がまだ呼び出されていない場合、2番目のスレッドがスケジュールされます
Shivam Aggarwal

148

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

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(condition)を使用してください。

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

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

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

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

5)最後に、しかし重要ではありませんが、Java Concurrency in Practiceを読んでください!


1
「if(!pizzaArrived){wait();}」を使用しない理由について詳しく教えてください。
全員、

2
@Everyone:説明を追加しました。HTH。
Enno Shioji

1
なぜpizzaArrivedフラグを使用するのですか?フラグが呼び出されずに変更された場合、notify効果はありません。また、with waitand notifycallの例でも機能します。
パブロフェルナンデス

2
わかりません-スレッド1がeatPizza()メソッドを実行し、最上位の同期ブロックに入り、MyHouseクラスで同期します。まだピザが届いていないので、そのまま待ちます。これで、スレッド2はpizzaGuy()メソッドを呼び出してピザの配達を試みます。ただし、スレッド1がすでにロックを所有していて、ロックをあきらめない(永続的に待機している)ため、これを行うことはできません。事実上、結果はデッドロックです。スレッド1はスレッド2がnotifyAll()メソッドを実行するのを待機し、スレッド2はスレッド1がMyHouseクラスのロックを放棄するのを待機しています...何が欠けているのですかここに?
flamming_python 2014

1
いいえ、変数がsynchronizedキーワードで保護されている場合、変数を宣言するのは冗長ですvolatile。混乱を避けるために、変数を回避することをお勧めします@mrida
Enno

37

あなたが求めたにもかかわらず wait()およびnotify()具体的には、私はこの引用は、まだ十分に重要であると感じます。

Josh Bloch、Effective Java 2nd Edition、Item 69:Prefer concurrency Utilities towaitnotify(強調):

使用しての難しさを考えるwaitnotify、正しく、あなたではなく、より高いレベルの並行処理ユーティリティを使用する必要があります [...]使用waitしてnotify直接することにより提供されるより高いレベルの言語と比較して、「並行処理アセンブリ言語」でプログラミングのようなものですjava.util.concurrent理由の使用に、これまでであれば、ほとんどありませんwaitし、notify新しいコードで


java.util.concurrentパッケージで提供されるBlockingQueueSは永続的ではありません。キューを永続化する必要がある場合、何を使用できますか?つまり、キューに20個のアイテムがある状態でシステムがダウンした場合、システムの再起動時にそれらが存在する必要があります。java.util.concurrentのキューはすべて「メモリ内」にあるように見えるだけなので、これらをそのまま/ハッキング/オーバーライドして、永続化が可能な実装を提供する方法はありますか?
Volksman 2013年

1
おそらく、バッキングキューを提供できますか?つまり、永続的なQueueインターフェース実装を提供します。
Volksman、2013年

あなたが使用する必要はないだろうことは、この文脈で言及にこれは非常に良好であるnotify()wait()再び
Chaklader Asfak Arefe

7

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

さらに、この種のものを実際のソフトウェアで操作することは絶対に避けてください。それを試してみると良いので、それが何であるかを理解してください。他の人のためのソフトウェアを構築している場合は、より高いレベルの抽象化と同期されたコレクションまたはJMSキューを使用することをお勧めします。

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


2

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

スレッディングでの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());
   }
}

1
plzこの行を再確認してくださいif(arrayList.size() == 0)、ここでは間違いかもしれません。
Wizmann、2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.