1. ホーム
  2. java

[解決済み】javaでwait()とnotify()を使った簡単なシナリオ

2022-04-23 18:55:56

質問

簡単なシナリオ、つまりチュートリアルを用意して、Queueをどのように使うか具体的に教えてください。

どのように解決するのですか?

その 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() が同期されたコード領域内にある場合 ( wait()notify() の呼び出しが同じオブジェクト上で同期化されていることを意味します)。この理由は(標準的なスレッドセーフの懸念以外に)、ミスシグナルと呼ばれるものによるものです。

この例として、あるスレッドが put() キューが満杯になると、その状態をチェックし、キューが満杯であることを確認します。 しかし、ブロックする前に、別のスレッドがスケジュールされます。この2番目のスレッドは、次に take() キューから要素を取得し、待機中のスレッドにキューが満杯でなくなったことを通知します。しかし、最初のスレッドはすでにその状態をチェックしているので、単に wait() を実行し、再スケジュールされた後、たとえそれが進展していたとしても。

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

次に、スプリアスウェイクアップと呼ばれる問題のために、チェックする条件をif文ではなくwhileループに入れる必要があります。この場合、待機中のスレッドが notify() が呼び出されます。このチェックを while ループに入れることで、もし偽のウェイクアップが発生した場合、条件が再チェックされ、スレッドから wait() を再び使用する。


他の回答にもあるように、Java 1.5では新しい並行処理ライブラリ( java.util.concurrent パッケージ)で、wait/notify機構をより高度に抽象化するように設計されています。これらの新機能を使えば、元の例を次のように書き換えることができます。

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並行処理の実践 並行処理に関連する問題と解決策について知りたいことがすべて網羅されているからです。