409

「 と の違い 」を Google で検索するnotify()notifyAll()、多くの説明が表示されます (javadoc の段落は省略します)。それはすべて、ウェイクアップされている待機中のスレッドの数に要約さnotify()notifyAll()ます。

ただし(これらの方法の違いを正しく理解している場合)、さらにモニターを取得するために常に1つのスレッドのみが選択されます。最初のケースでは VM によって選択されたもの、2 番目のケースではシステム スレッド スケジューラによって選択されたものです。両方の正確な選択手順 (一般的な場合) は、プログラマーにはわかりません。

では、notify( )notifyAll()の有用な違いは何ですか? 何か不足していますか?

4

26 に答える 26

364

明らかにnotify、待機セット内の (任意の) 1 つのスレッドをnotifyAllウェイクし、待機セット内のすべてのスレッドをウェイクします。次の議論は、疑問を解決するはずです。notifyAllほとんどの場合に使用する必要があります。どちらを使用すればよいかわからない場合は、 を使用してnotifyAllください。以下の説明を参照してください。

非常に注意深く読んで理解してください。ご不明な点がございましたら、メールでお問い合わせください。

プロデューサー/コンシューマーを見てください (仮定は 2 つのメソッドを持つ ProducerConsumer クラスです)。IT IS BROKEN (使用しているためnotify) - はい、動作する可能性があります - ほとんどの場合でも動作しますが、デッドロックを引き起こす可能性もあります - その理由は次のとおりです。

public synchronized void put(Object o) {
    while (buf.size()==MAX_SIZE) {
        wait(); // called if the buffer is full (try/catch removed for brevity)
    }
    buf.add(o);
    notify(); // called in case there are any getters or putters waiting
}

public synchronized Object get() {
    // Y: this is where C2 tries to acquire the lock (i.e. at the beginning of the method)
    while (buf.size()==0) {
        wait(); // called if the buffer is empty (try/catch removed for brevity)
        // X: this is where C1 tries to re-acquire the lock (see below)
    }
    Object o = buf.remove(0);
    notify(); // called if there are any getters or putters waiting
    return o;
}

まず、

待機の前後に while ループが必要なのはなぜですか?

whileこの状況が発生した場合に備えて、ループが必要です。

waitコンシューマー 1 (C1) は同期ブロックに入り、バッファーは空であるため、C1 は (呼び出しを介して) 待機セットに入れられます。コンシューマー 2 (C2) は同期メソッドに入ろうとしていますが (上記の Y の時点)、プロデューサー P1 はオブジェクトをバッファーに入れ、続いて を呼び出しますnotify。唯一の待機中のスレッドは C1 であるため、起動され、ポイント X (上記) でオブジェクト ロックを再取得しようとします。

ここで、C1 と C2 が同期ロックを取得しようとしています。そのうちの 1 つが (非決定論的に) 選択されてメソッドに入り、もう 1 つはブロックされます (待機していませんが、ブロックされ、メソッドのロックを取得しようとしています)。C2 が最初にロックを取得するとします。C1 はまだブロックしています (X でロックを取得しようとしています)。C2 はメソッドを完了し、ロックを解放します。ここで、C1 がロックを取得します。whileC1 がループ チェック (ガード) を実行し、バッファから存在しない要素を削除することを防がれている (C2 は既にそれを取得している!) ため、ループがあるのは幸運なことだと思います。がなかった場合、C1 がバッファから最初の要素を削除しようとすると、 が取得されますwhileIndexArrayOutOfBoundsException

今、

さて、なぜnotifyAllが必要なのでしょうか?

上記の生産者/消費者の例では、notify. プロデューサーとコンシューマーの待機ループのガードが相互に排他的であることを証明できるため、このように見えます。つまり、putメソッドだけでなくメソッドでもスレッドを待機させることはできないようです。これgetが真であるためには、次のことが真である必要があるためです。

buf.size() == 0 AND buf.size() == MAX_SIZE(MAX_SIZE は 0 ではないと仮定)

しかし、これでは十分ではなく、 を使用する必要がありますnotifyAll。理由を見てみましょう...

サイズ 1 のバッファーがあるとします (例をわかりやすくするため)。次の手順は、デッドロックにつながります。スレッドが通知で起動されるときはいつでも、JVM によって非決定論的に選択される可能性があることに注意してください。つまり、待機中のスレッドを起動することができます。また、複数のスレッドがメソッドへのエントリでブロックしている (つまり、ロックを取得しようとしている) 場合、取得の順序が非決定論的である可能性があることに注意してください。また、スレッドは一度に 1 つのメソッドにしか存在できないことに注意してください。同期メソッドでは、クラス内の任意の (同期された) メソッドを実行 (つまり、ロックを保持) できるスレッドは 1 つだけです。次の一連のイベントが発生すると、デッドロックが発生します。

ステップ 1:
- P1 が 1 文字をバッファーに入れる

ステップ 2:
- P2 の試行put- 待機ループのチェック - すでに char - 待機

ステップ 3:
- P3 の試行put- 待機ループのチェック - すでに char - 待機

ステップ 4:
- C1 は 1 文字の取得を
試みます - C2 は 1 文字の取得を試みます-getメソッドへの入り口でブロックし
ます - C3 は 1 文字の取得を試みます - メソッドへの入り口でブロックしgetます

ステップ 5:
- C1 はgetメソッドを実行しています - char を取得し、メソッドを呼び出します - P2 をウェイクアップしnotifyます
- しかし 、C2 は P2 ができる前にメソッドに入ります (P2 はロックを再取得する必要があります)。そのため、P2 はメソッドへのエントリをブロックします - C2待機ループをチェックし、バッファに文字がないため待機 - C3 は C2 の後、P2 の前にメソッドに入り、待機ループをチェックし、バッファに文字がないため待機しますnotify
put

ステップ 6:
- 今: P3、C2、C3 が待っています!
- 最後に P2 がロックを取得し、char をバッファに入れ、notify を呼び出し、メソッドを終了します。

ステップ 7:
- P2 の通知によって P3 がウェイクされます (任意のスレッドをウェイクアップできることに注意してください)

- NOTIFY を呼び出すスレッドがなくなり、3 つのスレッドが永久に中断されます。

解決策:プロデューサー/コンシューマー コード (上記)notifyをに置き換えます。notifyAll

于 2010-07-06T12:58:36.130 に答える
265

ただし(これらの方法の違いを正しく理解している場合)、さらにモニターを取得するために常に1つのスレッドのみが選択されます。

それは正しくありません。 呼び出しでブロックされているすべてのスレッドをo.notifyAll()起こします。スレッドは1 つずつ戻ることできますが、それぞれ順番が回ります。o.wait()o.wait()


簡単に言えば、スレッドが通知を待機している理由によって異なります。何かが起こったことを待機中のスレッドの 1 つに伝えたいですか、それともすべてのスレッドに同時に伝えたいですか?

場合によっては、待機が終了すると、待機中のすべてのスレッドが有用なアクションを実行できます。例として、特定のタスクが終了するのを待っている一連のスレッドがあります。タスクが完了すると、待機中のすべてのスレッドがビジネスを続行できます。そのような場合は、notifyAll()を使用して、待機中のすべてのスレッドを同時にウェイクアップします。

相互排他ロックなどの別のケースでは、待機中のスレッドの 1 つだけが、通知を受けた後に何か有用なことを実行できます (この場合はロックを取得します)。そのような場合は、むしろnotify()を使用します。適切に実装すれば、この状況でもnotifyAll()を使用できますが、とにかく何もできないスレッドを不必要に起こすことになります。


多くの場合、条件を待機するコードはループとして記述されます。

synchronized(o) {
    while (! IsConditionTrue()) {
        o.wait();
    }
    DoSomethingThatOnlyMakesSenseWhenConditionIsTrue_and_MaybeMakeConditionFalseAgain();
}

そのようにして、o.notifyAll()呼び出しが複数の待機中のスレッドをウェイクアップし、make から最初に返されたo.wait()スレッドが条件を false 状態のままにした場合、ウェイクアップされた他のスレッドは待機状態に戻ります。

于 2008-08-31T19:25:22.790 に答える
47

便利な違い:

  • 待機中のすべてのスレッドが交換可能な場合 (ウェイクアップの順序は重要ではない)、または待機中のスレッドが 1 つしかない場合は、notify()を使用します。一般的な例は、キューからジョブを実行するために使用されるスレッド プールです。ジョブが追加されると、スレッドの 1 つが起動して次のジョブを実行し、スリープ状態に戻るように通知されます。

  • 待機中のスレッドが異なる目的を持ち、同時に実行できる必要がある場合には、notifyAll()を使用します。たとえば、複数のスレッドがリソースにアクセスする前に操作の完了を待機している共有リソースのメンテナンス操作です。

于 2008-08-31T19:41:28.740 に答える
19

それは、資源がどのように生産され消費されるかにかかっていると思います。一度に 5 つの作業オブジェクトが利用可能で、5 つのコンシューマー オブジェクトがある場合、notifyAll() を使用してすべてのスレッドをウェイクアップし、それぞれが 1 つの作業オブジェクトを処理できるようにすることは理にかなっています。

使用可能な作業オブジェクトが 1 つしかない場合、すべてのコンシューマー オブジェクトを起動してその 1 つのオブジェクトを奪い合うことに何の意味があるでしょうか? 利用可能な作業をチェックする最初のスレッドがそれを取得し、他のすべてのスレッドがチェックして、何もする必要がないことを発見します。

ここで素晴らしい説明を見つけました。要するに:

notify() メソッドは一般にリソース プールに使用され、リソースを取得する任意の数の「コンシューマー」または「ワーカー」が存在しますが、リソースがプールに追加されると、待機中のコンシューマーまたはワーカーの 1 つだけが処理できます。それと。notifyAll() メソッドは、実際には他のほとんどの場合に使用されます。厳密には、複数のウェイターが続行できる状態をウェイターに通知する必要があります。しかし、これを知るのは難しいことがよくあります。したがって、一般的な規則として、notify() を使用するための特定のロジックがない場合は、おそらく notifyAll()を使用する必要があります。これは、特定のオブジェクトでどのスレッドが待機しているか、またその理由を正確に把握することが難しい場合が多いためです。

于 2008-08-31T19:25:10.763 に答える
12

同時実行ユーティリティを使用するsignal()signalAll()、これらのメソッドが呼び出されるときに と のどちらかを選択できることに注意してください。そのため、質問は であっても有効ですjava.util.concurrent

Doug Lea は、彼の有名な本の中で興味深い点を取り上げてます。これが発生する可能性があり、劇的な影響がある場合は、オーバーヘッドの代償を払っても安全な選択です (ほとんどの場合、あまりにも多くのスレッドを起こします)。notify()Thread.interrupt()notifyAll()

于 2009-06-17T11:41:37.257 に答える
10

ここに例があります。それを実行します。次に、notifyAll() の 1 つを notify() に変更して、何が起こるかを確認します。

ProducerConsumerExample クラス

public class ProducerConsumerExample {

    private static boolean Even = true;
    private static boolean Odd = false;

    public static void main(String[] args) {
        Dropbox dropbox = new Dropbox();
        (new Thread(new Consumer(Even, dropbox))).start();
        (new Thread(new Consumer(Odd, dropbox))).start();
        (new Thread(new Producer(dropbox))).start();
    }
}

ドロップボックス クラス

public class Dropbox {

    private int number;
    private boolean empty = true;
    private boolean evenNumber = false;

    public synchronized int take(final boolean even) {
        while (empty || evenNumber != even) {
            try {
                System.out.format("%s is waiting ... %n", even ? "Even" : "Odd");
                wait();
            } catch (InterruptedException e) { }
        }
        System.out.format("%s took %d.%n", even ? "Even" : "Odd", number);
        empty = true;
        notifyAll();

        return number;
    }

    public synchronized void put(int number) {
        while (!empty) {
            try {
                System.out.println("Producer is waiting ...");
                wait();
            } catch (InterruptedException e) { }
        }
        this.number = number;
        evenNumber = number % 2 == 0;
        System.out.format("Producer put %d.%n", number);
        empty = false;
        notifyAll();
    }
}

消費者クラス

import java.util.Random;

public class Consumer implements Runnable {

    private final Dropbox dropbox;
    private final boolean even;

    public Consumer(boolean even, Dropbox dropbox) {
        this.even = even;
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            dropbox.take(even);
            try {
                Thread.sleep(random.nextInt(100));
            } catch (InterruptedException e) { }
        }
    }
}

プロデューサークラス

import java.util.Random;

public class Producer implements Runnable {

    private Dropbox dropbox;

    public Producer(Dropbox dropbox) {
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            int number = random.nextInt(10);
            try {
                Thread.sleep(random.nextInt(100));
                dropbox.put(number);
            } catch (InterruptedException e) { }
        }
    }
}
于 2010-05-21T03:34:59.097 に答える
9

Joshua Blochから、Effective Java 2ndeditionのJavaGuru自身:

「項目69:待機して通知するために並行性ユーティリティを優先する」。

于 2008-09-03T14:26:12.313 に答える
5

悪名高い「ウェイクアップの喪失」の問題について誰も言及していないことに非常に驚いています(グーグルで)。

基本的に:

  1. 同じ条件で複数のスレッドが待機している場合、
  2. 状態Aから状態Bに移行できる複数のスレッド、および
  3. 状態Bから状態Aに移行できる複数のスレッド(通常は1と同じスレッド)および、
  4. 状態AからBに移行すると、1のスレッドに通知する必要があります。

次に、ウェイクアップの喪失が不可能であるという証明可能な保証がない限り、notifyAllを使用する必要があります。

一般的な例は、次のような並行FIFOキューです。複数のエンキューア(上記の1.および3.)がキューを空から空でない状態に移行できる複数のデキューア(上記の2.)が「キューが空ではない」という条件を待つことができる->空でない場合はデキューに通知する必要があります

空のキューから開始して、2つのエンキューアと2つのデキューアが相互作用し、1つのエンキューアがスリープ状態のままになる操作のインターリーブを簡単に記述できます。

これは、デッドロックの問題にほぼ匹敵する問題です。

于 2012-09-15T16:50:48.943 に答える
4

notify()よりも効率的なコードを記述できますnotifyAll()

複数の並列スレッドから実行される次のコードを検討してください。

synchronized(this) {
    while(busy) // a loop is necessary here
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    notifyAll();
}

以下を使用して、より効率的にすることができますnotify()

synchronized(this) {
    if(busy)   // replaced the loop with a condition which is evaluated only once
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    notify();
}

多数のスレッドがある場合、または待機ループ条件の評価にコストがかかる場合は、notify()よりも大幅に高速になりnotifyAll()ます。たとえば、1000 個のスレッドがある場合、999 個のスレッドが起動され、最初のnotifyAll(). 逆に、notify()ソリューションでは、1 つのスレッドのみが目覚めます。

notifyAll()次に作業を行うスレッドを選択する必要がある場合に 使用します。

synchronized(this) {
    while(idx != last+1)  // wait until it's my turn
        wait();
}
...
synchronized(this) {
    last = idx;
    notifyAll();
}

最後に、 の場合、起動されたブロックnotifyAll()内のコードsynchronizedは、一度にすべてではなく、順次実行されることを理解することが重要です。上記の例で 3 つのスレッドが待機していて、4 番目のスレッドが を呼び出しているとしますnotifyAll()。3 つのスレッドすべてが起動されますが、実行を開始してwhileループの状態をチェックするのは 1 つだけです。条件が の場合、再びtrue呼び出さwait()れ、2 番目のスレッドが実行を開始し、そのwhileループ条件がチェックされます。

于 2013-06-17T23:23:24.293 に答える
4

notify()すべてのスレッドをウェイクアップしながら、1 つのスレッドをnotifyAll()ウェイクアップします。私の知る限り、妥協点はありません。notify()ただし、スレッドがどうなるかわからない場合は、 を使用してくださいnotifyAll()。毎回魅力のように機能します。

于 2008-08-31T19:19:21.713 に答える
4

notify()- オブジェクトの待機セットからランダムにスレッドを選択し、BLOCKED状態にします。オブジェクトの待機セット内の残りのスレッドは、まだそのWAITING状態にあります。

notifyAll()- すべてのスレッドをオブジェクトの待機セットからBLOCKED状態に移動します。を使用した後notifyAll()、共有オブジェクトの待機セットに残っているスレッドはありません。これは、すべてのスレッドが inBLOCKED状態であり、in 状態ではないためWAITINGです。

BLOCKED- ロック取得のためにブロックされました。 WAITING- 通知待ち (または参加完了をブロック)。

于 2015-10-25T12:56:57.500 に答える
4

私の知る限り、上記の答えはすべて正しいので、別のことをお話しします。本番コードの場合は、java.util.concurrent のクラスを実際に使用する必要があります。Java の並行性の領域では、彼らができないことはほとんどありません。

于 2008-08-31T19:50:58.530 に答える
2

@xagyg によって投稿されたコードを見てください。

2 つの異なるスレッドが 2 つの異なる条件を待機しているとします
最初のスレッドは を待機してbuf.size() != MAX_SIZEおり、2 番目のスレッドは を待機していbuf.size() != 0ます。

ある時点buf.size() で が 0 に等しくないとしますnotify()の代わりにJVM が呼び出さnotifyAll()れ、最初のスレッドに通知されます (2 番目のスレッドではありません)。

最初のスレッドが起動され、buf.size()どれが を返す可能性があるかをチェックしMAX_SIZE、待機状態に戻ります。2 番目のスレッドは起動されず、待機を続け、 を呼び出しませんget()

于 2012-03-26T21:45:56.690 に答える
0

notify()wait()同じオブジェクトを呼び出した最初のスレッドを起こします。

notifyAll()wait()同じオブジェクトを呼び出したすべてのスレッドを起こします。

最も優先順位の高いスレッドが最初に実行されます。

于 2011-12-05T18:49:15.600 に答える
0

「オブジェクト」のwait()を呼び出すと(オブジェクトロックが取得されることを期待して)、internはそのオブジェクトのロックを解放し、他のスレッドがこの「オブジェクト」をロックするのを助けます。このシナリオでは、 「リソース/オブジェクト」を待機している複数のスレッド(他のスレッドも同じ上記のオブジェクトで待機を発行し、リソース/オブジェクトを埋めてnotify/notifyAllを呼び出すスレッドが存在することを考慮してください)。

ここで、同じオブジェクトの通知を (プロセス/コードの同じ/反対側から) 発行すると、ブロックされて待機している単一のスレッドが解放されます (すべての待機スレッドではありません。この解放されたスレッドは JVM スレッドによって選択されます)。スケジューラとオブジェクトのすべてのロック取得プロセスは通常と同じです)。

このオブジェクトを共有/処理するスレッドが 1 つしかない場合は、wait-notify 実装で notify() メソッドを単独で使用しても問題ありません。

ビジネスロジックに基づいて、複数のスレッドがリソース/オブジェクトを読み書きする状況にある場合は、notifyAll() を使用する必要があります。

今、オブジェクトで notify() を発行したときに、jvm が待機中のスレッドをどのように識別して中断しているかを調べています ...

于 2016-04-21T18:32:01.033 に答える
0

上記のいくつかの確かな答えがありますが、私が読んだ混乱と誤解の数に驚いています. これはおそらく、独自の壊れた並行コードを作成しようとするのではなく、可能な限り java.util.concurrent を使用する必要があるという考えを証明しています。

質問に戻ります。要約すると、現在のベスト プラクティスは、ウェイクアップが失われるため、すべての状況で notify() を回避することです。これを理解していない人は、ミッション クリティカルな同時実行コードを書くことを許可されるべきではありません。ハーディングの問題が心配な場合、一度に 1 つのスレッドを起動する安全な方法の 1 つは、次のとおりです。

  1. 待機スレッドの明示的な待機キューを構築します。
  2. キュー内の各スレッドに先行スレッドを待機させます。
  3. 完了したら、各スレッドで notifyAll() を呼び出します。

または、すでにこれを実装している Java.util.concurrent.* を使用することもできます。

于 2017-01-28T14:03:05.903 に答える
-2

ここでは、すべてを目覚めさせることはあまり重要ではありません。notifyとnotifyallを待ちます。これらはすべて、オブジェクトのモニターを所有した後に配置されます。スレッドが待機段階にあり、notifyが呼び出された場合、このスレッドはロックを取得し、その時点で他のスレッドはそのロックを取得できません。そのため、同時アクセスはまったく実行できません。私の知る限り、notifyとnotifyallを待機する呼び出しは、オブジェクトをロックした後でのみ行うことができます。私が間違っている場合は私を訂正してください。

于 2009-12-14T08:17:21.180 に答える