Java:notify()とnotifyAll()の繰り返し


377

もし「の違いのための1つのグーグルnotify()notifyAll()」その後の説明の多くは、(離れてjavadocの段落を残して)ポップアップ表示されます。すべては、ウェイクアップされる待機中のスレッドの数に要約さnotify()notifyAll()ます。

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

に有用との違いは、通知()のnotifyAll() 、その後?何か不足していますか?


6
並行処理に使用する有用なライブラリは並行処理ライブラリにあります。ほとんどの場合、これらを選択することをお勧めします。ConcurencyライブラリはJava 5.0より前のもの(2004年に標準として追加されたもの)
Peter Lawrey、

4
私はピーターに同意しません。並行処理ライブラリはJavaで実装され、あなたが代わりに、古き良きの並行処理ライブラリを使用して足で自分自身を撮影することができますなど、Javaコードの多くは、あなたがロックを呼び出すたびに()が実行ロック解除()が存在しているsynchronized特定のを除いて、 、かなりまれなユースケース。
Alexander Ryzhov 2013年

2
重要な誤解は次のように思われます。...モニターを取得するために常に1つのスレッドのみが選択されます。最初のケースではVMによって選択されたもの、2番目のケースではシステムスレッドスケジューラによって選択されたもの。本質的に同じであるという意味です。説明されている動作は正しいですが、不足しているのはnotifyAll()、最初のスレッドが起動した後、他のスレッドが1つずつモニターを取得することです。このnotify場合、他のどのスレッドも起こされません。機能的にはとても異なります!
BeeOnRope 2017年

1)オブジェクトで多くのスレッドが待機していて、notify()がそのオブジェクトで1回だけ呼び出された場合。待機中のスレッドの1つを除いて、残りのスレッドは永久に待機しますか?2)notify()が使用される場合、待機中の多くのスレッドの1つだけが実行を開始します。notifyall()を使用すると、待機中のすべてのスレッドに通知されますが、そのうちの1つだけが実行を開始するので、ここでnotifyall()を使用するとどうなりますか?
Chetan Gowda

@ChetanGowdaすべてのスレッドを通知するvs正確に通知する任意の1つのスレッドのみが実際に大きな違いを持っ​​ているように見えますが、その違いはわずかですが重要な違いがあります。notify()を1つだけスレッドに通知すると、他のすべてのスレッドは明示的な通知を受け取るまで待機状態になります/信号。すべてを通知し、すべてのスレッドが任意の更なる通知なしに、他の後に実行し、いくつかのための1に完成予定-ここでは、スレッドがあると言うべきでblockedはないwaiting.When blocked別のスレッドが内側になるまで、その幹部は一時的に停止されるsyncブロック。
user104309

回答:


248

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

不正解です。 o.notifyAll()ウェイク全てにブロックされたスレッドのo.wait()呼び出しを。スレッドはo.wait()1つずつ戻ることができますが、それぞれの順番になります。


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

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

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


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

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

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


29
1つのスレッドだけに通知し、複数のオブジェクトで待機している場合、VMはどのスレッドに通知するかをどのように決定しますか?
両生類2013

6
Java仕様についてははっきりとは言えませんが、一般的には、そのような詳細についての想定を避ける必要があります。ただし、VMは正常でほぼ公正な方法でそれを行うと想定できます。
リードマン2013

15
Liedmanはひどく間違っています。Java仕様では、notify()が公平であることが保証されていないことが明示されています。つまり、notifyを呼び出すたびに同じスレッドが再び起動する可能性があります(モニターのスレッドキューはFAIRまたはFIFOではありません)。ただし、スケジューラは公平であることが保証されています。これが、2つを超えるスレッドがあるほとんどの場合に、notifyAllを優先する必要がある理由です。
Yann TM

45
@YannTM私はすべて建設的な批評家ですが、あなたの口調は少し不公平だと思います。はっきりとは言えないけど「思う」と。7年前に100%正しくなかった何かを書いたことがありますか。
Liedman、2015

10
問題は、これが受け入れられた答えであることです。それは個人のプライドの問題ではありません。あなたが今間違っていたことがわかっている場合は、答えを編集してそれを言うようにしてください。そして、例えばxagyg pedagogicと下の正しい答えをポイントしてください。
Yann TM

330

明らかnotifyに、待機セット内の(任意の)スレッドを起動し、待機セット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この状況が発生した場合に備えて、ループが必要です。

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

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

今、

では、なぜnotifyAllが必要なのでしょうか。

上記のプロデューサー/コンシューマーの例では、を回避できるように見えnotifyます。これは、プロデューサーとコンシューマーの待機ループのガードが相互に排他的であることを証明できるため、このように見えます。つまり、putメソッドとメソッドの両方でスレッドを待機させることはできないように見えますget。なぜなら、それがtrueになるためには、以下がtrueでなければならないからです。

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

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

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

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

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

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

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

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

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

ステップ7:
-P2の通知がP3を呼び起こします(スレッドは起こされる可能性があることに注意してください)-P3
は待機ループ条件をチェックします。バッファーにすでに文字があるため、待機します。
-通知を呼び出すスレッドがなくなり、3つのスレッドが永久に停止しました!

【解決交換notifynotifyAllプロデューサ/コンシューマ・コード(上記)です。


1
finnw-P3 notify(この例では選択されたスレッド)が待機していたポイントから(whileループ内で)処理を続行するため、P3 は条件を再確認する必要があります。デッドロックを引き起こさない他の例もありますが、この場合、の使用はnotifyデッドロックのないコードを保証するものではありません。の使用notifyAll
xagyg 2010

4
@marcus非常に近い。notifyAllを使用すると、各スレッドは(一度に1つずつ)ロックを再取得しますが、1つのスレッドがロックを再取得してメソッドを実行した後(終了した後)...次のスレッドがロックを再取得し、「while」を確認します。そして、「待機」に戻ります(もちろん、条件によって異なります)。したがって、notifyは1つのスレッドを呼び起こします。notifyAllはすべてのスレッドを起動し、各スレッドは一度に1つずつロックを再取得します。「while」の状態を確認し、メソッドを実行するか、もう一度「待機」します。
xagyg

1
@xagyg、あなたはすべてのプロデューサーが格納する単一のcharを1つだけ持っているシナリオについて話していますか?もしそうなら、あなたの例は正しいですが、あまり興味深いIMOではありません。私が提案する追加の手順を使用すると、同じシステムをデッドロックすることができますが、無制限の入力を使用できます。これが、このようなパターンが実際に実際に使用されている方法です。
eran

3
@codeObserverあなたが尋ねた:「notifyAll()を呼び出すと、同時にwhile()条件をチェックする複数の待機中のスレッドが発生します..そのため、whileの前に2つのスレッドがすでに終了し、outOfBoundが発生している可能性があります。例外?」いいえ、これは不可能です。複数のスレッドが起動しますが、同時にwhile条件をチェックすることはできないためです。コードセクションを再入力してwhileを再確認する前に、(待機の直後に)ロックを再取得する必要があります。したがって、1つずつ。
xagyg

4
@xagygいい例。これは元の質問のトピックから外れています。議論のためだけに。デッドロックは設計上の問題です(間違っている場合は修正してください)。putとgetの両方で1つのロックを共有しているからです。また、JVMは、getがロックを解放した後にputを呼び出すほどスマートではありません。逆も同様です。putが別のputを起動し、while()のために自分自身をwait()に戻すため、デッドロックが発生します。2つのクラス(および2つのロック)を機能させますか?。。{(GET)synchonized}、{((PUT)synchonized}を取得言い換えれば、GETだけ入れ、プットは取得のみウェイクアップします目を覚ますだろうプットだから
ジェイ

43

便利な違い:

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

  • 待機中のスレッドに異なる目的があり、同時に実行できる必要がある他の場合には、notifyAll()を使用してください。例としては、リソースにアクセスする前に複数のスレッドが操作の完了を待機している共有リソースの保守操作があります。


19

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

使用可能な作業オブジェクトが1つしかない場合、すべてのコンシューマオブジェクトをウェイクアップして、その1つのオブジェクトを競合させるポイントは何ですか。利用可能な作業をチェックする最初のスレッドがそれを取得し、他のすべてのスレッドがチェックして、何もする必要がないことを見つけます。

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

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


11

同時実行ユーティリティでは、これらのメソッドがそこで呼び出されるため、signal()との間で選択することもできますsignalAll()。したがって、質問はでも有効ですjava.util.concurrent

ダグ・リーは、彼の中に興味深いポイントアップもたらし有名な本を次の場合notify()とはThread.interrupt()同時に起こり、実際に失われる可能性があります通知します。これが発生する可能性があり、劇的な影響notifyAll()がある場合は、オーバーヘッドの代償を払っても(ほとんどの場合、スレッドが多すぎるため)、安全な選択です。


10

短い要約:

常に好むのnotifyAll()をオーバー()に通知しますが、多数のスレッドがすべて同じことを行う大規模な並列アプリケーションを持っていない限り。

説明:

notify() [...]は、単一のスレッドを起動します。notify()はウェイクアップするスレッドを指定することを許可しないので、それは大規模な並列アプリケーション(つまり、すべて同じような雑用を行う多数のスレッドを持つプログラム)でのみ役立ちます。このようなアプリケーションでは、どのスレッドが起こされるかは問題ではありません。

ソース:https : //docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

上記の状況でnotify ()notifyAll ()を比較してください。スレッドが同じことをしている超並列アプリケーションです。あなたが呼び出す場合のnotifyAll()を、その場合には、のnotifyAll()は、一つのスレッドだけが実際に付与されます、つまりスレッドを進めることができるので(不必要にそれらの多くをスレッドの膨大な数の目覚め(すなわちスケジューリング)を誘導しますオブジェクトwait()notify()、またはnotifyAll()が呼び出された)を監視しているため、コンピューティングリソースを浪費しています。

したがって、あなたが好む、スレッドの膨大な数が同時に同じことを行うアプリケーションを持っていない場合)のnotifyAllを(オーバー)(通知。どうして?他のユーザーがこのフォーラムですでに回答しているため、notify()

このオブジェクトのモニターで待機している単一のスレッドを起動します。[...]選択は任意であり、実装の裁量で行われます。

ソース:Java SE8 API(https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify--

コンシューマーが消費する準備ができて(つまり、wait() ing)、プロデューサーが生成する準備ができて(つまり、wait()して)、アイテムのキュー(生成/消費される)が空であるプロデューサーコンシューマーアプリケーションがあるとします。その場合、ウェイクアップするユーザーの選択は任意であるため、notify()はコンシューマーのみをウェイクアップし、プロデューサーはウェイクアップしない場合があります。生産者と消費者はそれぞれ生産と消費の準備ができていますが、生産者と消費者のサイクルは進展しません。代わりに、コンシューマはウェイクアップされ(つまり、wait()ステータスを残し)、アイテムが空であるためキューから取り出さず、notify()が別のコンシューマに処理を続行します。

対照的に、notifyAll()はプロデューサーとコンシューマーの両方を呼び起こします。スケジュールされる人の選択は、スケジューラーに依存します。もちろん、スケジューラーの実装によっては、スケジューラーはコンシューマーのみをスケジュールする場合もあります(例えば、コンシューマースレッドに非常に高い優先度を割り当てる場合)。しかし、ここでの仮定は、任意の合理的に実施し、スケジューラはちょうどことはありませんので、スケジューラのスケジューリングの危険性だけで消費者が消費者だけで目を覚ますJVMの危険性よりも低いことである任意の意思決定を。むしろ、ほとんどのスケジューラ実装は、飢餓を防ぐために少なくともある程度の努力をしています。


9

ここに例があります。それを実行します。次に、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();
    }
}

Dropboxクラス

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) { }
        }
    }
}

8

効果的なJava第2版のJavaグル自身であるJoshua Blochから:

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


16
なぜソースよりも重要です。
Pacerier 2014

2
@Pacerierまあ言った。その理由ももっと知りたいです。考えられる理由の1つは、オブジェクトクラスでの待機と通知が暗黙的な条件変数に基づいていることです。したがって、標準のプロデューサーとコンシューマーの例では、プロデューサーとコンシューマーの両方が同じ条件で待機するため、xagygの回答で説明されているように、デッドロックが発生する可能性があります。したがって、より良い方法は、docs.oracle.com
javase /

6

これでいくつかの疑問が解消されることを願っています。

notify():notify()メソッドは、ロックを待機している1つのスレッド(そのロックでwait()を呼び出した最初のスレッド)を起こします。

notifyAll():notifyAll()メソッドは、ロックを待機しているすべてのスレッドを起こします。JVMは、ロックを待機しているスレッドのリストからスレッドの1つを選択し、そのスレッドを起動します。

単一のスレッドがロックを待機している場合、 notify()とnotifyAll()の間に大きな違いはありません。ただし、複数のスレッドがロックを待機している場合、notify()とnotifyAll()の両方で、起動された正確なスレッドはJVMの制御下にあり、プログラムで特定のスレッドの起動を制御することはできません。

一見すると、notify()を呼び出して1つのスレッドを起動することをお勧めします。すべてのスレッドを起こす必要はないように見えるかもしれません。ただし、notify()の問題は、ウェイクアップしたスレッドがウェイクアップするのに適していない可能性があることです(スレッドは他の条件を待っているか、条件がまだそのスレッドに対して満たされていないなど)。その場合、notify()が失われる可能性があり、他のスレッドがウェイクアップして、一種のデッドロックにつながる可能性はありません(通知は失われ、他のすべてのスレッドは通知を永久に待機します)。

この問題を回避するには、ロックを待機している複数のスレッド(または待機が行われている複数の条件)がある場合は、常にnotifyAll()を呼び出す方が適切です。notifyAll()メソッドはすべてのスレッドを起動するため、あまり効率的ではありません。ただし、このパフォーマンスの低下は、実際のアプリケーションではごくわずかです。


6

スレッドには3つの状態があります。

  1. WAIT-スレッドはCPUサイクルを使用していません
  2. BLOCKED-スレッドは、モニターを取得しようとしてブロックされています。まだCPUサイクルを使用している可能性があります
  3. 実行中-スレッドは実行中です。

これで、notify()が呼び出されると、JVMは1つのスレッドを選択し、それらをBLOCKED状態に、したがってRUNNING状態に移動します。これは、モニターオブジェクトの競合がないためです。

notifyAll()が呼び出されると、JVMはすべてのスレッドを選択し、それらすべてをBLOCKED状態に移動します。これらのスレッドはすべて、優先的にオブジェクトのロックを取得します。最初にモニターを取得できるスレッドは、最初にRUNNING状態に移行できます。


ただ素晴らしい説明。
royatirek 2018

5

悪名高い「ウェイクアップが失われる」問題について誰も言及しなかったことに私は非常に驚いています(google it)。

基本的に:

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

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

一般的な例は、同時FIFOキューです。複数のエンキューヤー(上記の1.および3.)は、キューを空から空でない状態に移行できます。 ->空でない場合はデキュー元に通知する必要があります

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

これは、デッドロック問題とほぼ同等の問題です。


申し訳ありませんが、xagygが詳しく説明しています。この問題の名前は「失われたウェイクアップ」です
NickV

@Abhay Bansal:condition.wait()がロックを解放し、ウェイクアップしたスレッドによってロックが再取得されるという事実を見逃していると思います。
NickV 2012

4

notify()は1つのスレッドをnotifyAll()起動し、すべてのスレッドを起動します。私の知る限りでは、中立の立場はありません。ただしnotify()、スレッドに対して何が行われるかわからない場合は、を使用してくださいnotifyAll()。いつでも魅力のように機能します。


4

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


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の場合、最初のnotifyAll()、次に998、次に997のように、999スレッドが呼び起こされて評価されます。反対に、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つのスレッドすべてが起こされますが、1つだけが実行を開始し、whileループの状態をチェックします。条件がの場合true、それはwait()再び呼び出され、そのときのみ、2番目のスレッドが実行を開始してwhileループ条件をチェックします。


4

簡単な説明は次のとおりです。

notify()とnotifyAll()のどちらを使用しても、ただちに他の1つのスレッドがモニターを取得して実行を開始するというのは正しいことです。(一部のスレッドが実際にこのオブジェクトのwait()でブロックされていると仮定します。他の無関係なスレッドがすべての使用可能なコアを吸収していないなど)。影響は後で発生します。

スレッドA、B、およびCがこのオブジェクトを待機していて、スレッドAがモニターを取得するとします。違いは、Aがモニターを解放するとどうなるかです。notify()を使用した場合、BとCは引き続きwait()でブロックされます。これらはモニターで待機しておらず、通知されるのを待機しています。Aがモニターを解放しても、BとCはそこに座って、notify()を待ちます。

notifyAll()を使用した場合、BとCはどちらも「通知待ち」状態を超えて進み、両方ともモニターの取得を待機しています。Aがモニターを解放すると、BまたはCがモニターを取得し(他のスレッドがそのモニターをめぐって競合していないと想定)、実行を開始します。


非常に明確な説明。このnotify()の動作の結果として、「シグナルの欠落」/「通知の欠落」が発生し、アプリケーションの状態がデッドロック/進行状況にならない可能性があります。C1はnotify()を呼び出し、プロデューサーを対象としていますが、C2がウェイクアップされる可能性があるため、P1とP2の両方が通知を見逃し、さらに明示的な「通知」(notify()呼び出し)を待機します。
user104309

4

この答えは、によって優れた答えのグラフィカルな書き換えと簡素化されxagygのコメントを含め、エラン

各製品が1人の消費者向けである場合でも、なぜnotifyAllを使用するのですか?

次のように簡略化されたプロデューサーとコンシューマーを検討してください。

プロデューサー:

while (!empty) {
   wait() // on full
}
put()
notify()

消費者:

while (empty) {
   wait() // on empty
}
take()
notify()

サイズ1のバッファーを共有する2つのプロデューサーと2つのコンシューマーを想定します。次の図は、すべてのスレッドがnotifyAllを使用する場合に回避されるデッドロックにつながるシナリオを示しています。

各通知には、ウェイクアップされるスレッドのラベルが付けられています。

通知によるデッドロック


3

私はJava Concurrency in Practiceで説明されていることを述べたいと思います:

最初のポイント、NotifyかNotifyAllか?

It will be NotifyAll, and reason is that it will save from signall hijacking.

2つのスレッドAとBが同じ条件キューの異なる条件述部で待機していて、通知が呼び出された場合、スレッドJVMが通知するのはJVMまでです。

通知がスレッドAを対象としていて、JVMがスレッドBに通知した場合、スレッドBはウェイクアップし、この通知が役に立たないため、再び待機します。そして、スレッドAは、この見逃された信号について知ることはなく、誰かがその通知を乗っ取りました。

したがって、notifyAllを呼び出すと、この問題が解決しますが、すべてのスレッドに通知し、すべてのスレッドが同じロックを求めて競合し、コンテキストの切り替えとそれに伴うCPUの負荷がかかるため、パフォーマンスに影響があります。ただし、パフォーマンスに注意する必要があるのは、正しく動作する場合のみです。動作自体が正しくない場合、パフォーマンスは役に立ちません。

この問題は、jdk 5で提供される明示的なロックLockのConditionオブジェクトを使用して解決できます。条件オブジェクトごとに異なる待機が提供されるためです。ここでは正しく動作し、シグナルを呼び出し、その状態を待機しているスレッドが1つだけであることを確認するため、パフォーマンスの問題は発生しません。


3

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

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

BLOCKED-ロック取得のためにブロックされました。 WAITING-通知を待つ(または結合の完了をブロックする)。


3

効果的なJavaに関するブログからの引用:

The notifyAll method should generally be used in preference to notify. 

If notify is used, great care must be taken to ensure liveness.

だから、私が理解しているのは(前述のブログから、受け入れられた回答とJava ドキュメントに関する「Yann TM」によるコメント):

  • notify():JVMは、このオブジェクトで待機中のスレッドの1つを呼び起こします。スレッドの選択は、公平性なしに任意に行われます。そのため、同じスレッドを何度も目覚めさせることができます。したがって、システムの状態は変化しますが、実際の進展はありません。したがって、ライブロックを作成します
  • notifyAll():JVMはすべてのスレッドを呼び起こし、すべてのスレッドがこのオブジェクトのロックを求めて競合します。現在、CPUスケジューラは、このオブジェクトのロックを取得するスレッドを選択しています。この選択プロセスは、JVMによる選択よりもはるかに優れています。したがって、活力を確保します。

2

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

:2つの異なるスレッドが2つの異なる条件を待っていると仮定最初のスレッドが待っている、及び第二のスレッドが待っています。
buf.size() != MAX_SIZEbuf.size() != 0

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

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


1

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

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

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


13
notify()それが正確に「最初のスレッド」でない場合。
Bhesh Gurung 2011

6
VMによってどれが選択されるかを予測することはできません。神様しか分からない。
Sergii Shevchyk

誰が最初になるかは保証されていません(公平ではありません)
Ivan Voroshilin '23 / 09/14

OSが保証している場合にのみ最初のスレッドを起動しますが、そうでない可能性があります。起動するスレッドを決定するのは、OS(およびそのスケジューラー)によるものです。
ポールステリアン

1

notifyは、待機状態のスレッドを1つだけ通知しますが、notify allは、待機状態のすべてのスレッドに通知します。通知されたすべてのスレッドとブロックされたすべてのスレッドがロックに適格であり、そのうち1つだけがロックを取得し、他のすべて(以前に待機状態にあるものを含む)はブロック状態になります。


1

上記の優れた詳細な説明を要約すると、私が考えることができる最も簡単な方法で、これは1)同期ユニット全体(ブロックまたはオブジェクト)で取得されるJVM組み込みモニターの制限と2)によるものです。待機中/通知中/通知中の特定の条件を区別しません。

これは、複数のスレッドが異なる条件で待機していて、notify()が使用されている場合、選択されたスレッドは、新しく満たされた条件で進行するスレッドではない可能性があることを意味します。条件などを満たすため)。進行できず、最終的には飢餓またはプログラムのハングアップ。

対照的に、notifyAll()を使用すると、待機中のすべてのスレッドが最終的にロックを再取得し、それぞれの状態を確認できるため、最終的には進行を許可できます。

したがって、notify()は、待機中のスレッドが選択された場合に進行を許可することが保証されている場合にのみ安全に使用できます。これは、通常、同じモニター内のすべてのスレッドが1つの同じ条件のみをチェックするときに満たされます-かなりまれです実際のアプリケーションでのケース。


0

「オブジェクト」のwait()を呼び出すと(オブジェクトロックが取得されることが期待されます)、これによりそのオブジェクトのロックが解除され、他のスレッドがこの「オブジェクト」をロックできるようになります。このシナリオでは、 「リソース/オブジェクト」を待機している複数のスレッド(他のスレッドも同じ上記のオブジェクトで待機を発行し、リソース/オブジェクトを満たし、notify / notifyAllを呼び出すスレッドが存在することを考えると)。

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

このオブジェクトで共有/動作するスレッドが1つしかない場合は、待機通知実装でnotify()メソッドのみを使用しても問題ありません。

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

今、私はオブジェクトに対してnotify()を発行したときに、jvmがどのようにして待機スレッドを正確に識別して破壊しているかを探しています...


0

上記のいくつかの確かな答えがありますが、私が読んだ混乱と誤解の数に驚いています。これはおそらく、独自の壊れた並行コードを作成するのではなく、java.util.concurrentをできるだけ使用するべきだという考えを証明しています。質問に戻ります。要約すると、今日のベストプラクティスは、ウェイクアップが失われる問題が原因で、すべての状況でnotify()を回避することです。これを理解していない人は、ミッションクリティカルな同時実行コードを書くことを許可されるべきではありません。群れの問題が心配な場合、一度に1つのスレッドを起動する安全な方法の1つは、次のとおりです。2.キュー内の各スレッドに、その先行スレッドを待機させます。3.完了したら、各スレッドでnotifyAll()を呼び出します。または、Java.util.concurrent。*、


私の経験では、待機/通知の使用は、スレッド(Runnable実装)がキューのコンテンツを処理するキューメカニズムでよく使用されます。wait()キューが空であるときはいつでも、次に使用されています。そして、notify()情報が追加されたときに呼び出されます。->このような場合、を呼び出すスレッドは1つだけです。待機しているスレッドが1つしかないことがわかってwait()いるnotifyAll()場合、を使用するのは少しばかげているように見えませんか。
bvdb 2017年

-2

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.