11

Java に典型的な生産者と消費者のパターンがあると想像してください。もう少し効率的にするために、新しい要素がキューに追加されたときではnotify()なく、使用したいと考えています。notifyAll()2 つのプロデューサー スレッドが通知を呼び出した場合、2 つの異なる待機中のコンシューマー スレッドが起動されることが保証されますか? それとも、2 つnotify()の s が互いに直後に起動されると、同じコンシューマ スレッドがウェイクアップのために 2 回キューに入れられる可能性がありますか? これが正確にどのように機能するかを説明するAPIのセクションが見つかりません。Javaには、スレッドを1回だけ起動するためのアトミックな内部操作がありますか?

待機しているコンシューマーが 1 つだけの場合、2 番目の通知は失われますが、問題はありません。

4

4 に答える 4

13

私の答えには、実装固有の情報がいくつかあります。これは、Sun JVM およびその他のスレッド ライブラリの動作に関する私の実務知識に基づいています。

2 つのプロデューサー スレッドが通知を呼び出した場合、2 つの異なる待機中のコンシューマー スレッドが起動されることが保証されますか?

いいえそうではありません。目覚めた消費者がいるという保証はありません。保証されているのは、待機中のスレッドが 2 つある場合、2 つの異なるスレッドが実行キューに入れられることです。

それとも、2 つnotify()の s が互いに直後に起動されると、同じコンシューマ スレッドがウェイクアップのために 2 回キューに入れられる可能性がありますか?

いいえ。2 つのnotify()呼び出しによって、同じコンシューマー スレッドが 2 回キューに入れられることはありません。ただし、1 つのスレッドが起動され、他のスレッドが待機していない可能性があるため、2 番目のnotify()呼び出しは何もしない可能性があります。もちろん、スレッドが目覚めた後、すぐに戻って再び待機し、2番目のnotify()呼び出しをそのように取得することもできますが、それがあなたが求めていることだとは思いません。

Javaには、スレッドを1回だけ起動するためのアトミックな内部操作がありますか?

はい。Threadコードには多数の同期ポイントがあります。スレッドが通知されると、waitキューから移動されます。今後の呼び出しnotify()waitキューを調べますが、スレッドは見つかりません。

もう1つの重要なポイント。while生産者/消費者モデルでは、ループで条件をテストしていることを常に確認してください。その理由は、ロックでブロックされているが条件を待機していないコンシューマーとの競合状態があるためです。

 synchronized (workQueue) {
     // you must do a while here
     while (workQueue.isEmpty()) {
         workQueue.wait();
     }
     workQueue.remove();
 }

Consumer1を待っている可能性がありますworkQueue。 実行キューでConsumer2ブロックされる可能性があります。synchronizedandに何かを入れるworkQueueworkQueue.notify()呼び出されます。 Consumer2は現在実行キューに入れられていますが、最初にそこにいた人より遅れています。 Consumer1これは一般的な実装です。そのため、通知され たConsumer1からアイテムを削除します。空であるかどうかを再度テストする必要があります。そうでない場合は、キューが再び空になるため、スローされます。レースの詳細はこちら。workQueueConsumer2Consumer2workQueueremove()

また、偽のウェイクアップが文書化されているため、呼び出しwhileなしでスレッドが起動されるのをループが防止することを認識することも重要です。wait()

BlockingQueueこれはすべて、他の回答で推奨されているようにを使用してプロデューサー/コンシューマーコードを削減できる場合は、そうする必要があります。このBlockingQueueコードは、これらの問題をすべて解決しています。

于 2012-05-21T12:40:07.200 に答える
3

はい、あなたが説明したことは起こる可能性があります。

javadocで説明されているようにnotify、任意のスレッドを起こします。そのため、スレッドが終了しwait、次の の前に呼び出されたnotify場合、それは任意のウェイクアップ候補の 1 つです。

私はマルチスレッド アプリケーションの経験が豊富で、次の 2 つのパターンのいずれかを使用しています。

  1. イベントでウェイクアップする必要がある複数のスリープ状態のスレッドがあり、それらがウェイクアップする順序は重要ではありません。この場合、私はnotifyAllそれらを目覚めさせるために使用します。

  2. イベントでウェイクアップする必要があるスリープ状態のスレッドが 1 つあります。この場合、私はnotifyそれを目覚めさせるために使用します。

複数のスリープ状態のスレッドがあり、そのうちの 1 つだけを起こしたい場合は、別の設計を使用してこれを実現します。基本的に、私は自分で何かを構築して、ランタイム環境によって恣意的な決定が行われないようにしています。私はいつも何が目を覚ますかを正確に知りたいと思っています。

設計をこれら 2 つのシナリオのいずれかに分解するか、java.util.concurrentパッケージの何かを使用します。偽の通知で問題が発生したことはありませんが、ロックに使用するオブジェクトにも細心の注意を払っています。Object私は、ロック操作のターゲットになることだけを目的としたバニラ インスタンスを作成する傾向がありますが、クラス タイプが明確に定義され、自分で制御できるオブジェクトを使用することもあります。

于 2012-05-21T11:48:50.443 に答える
2

ReentrantLockを使用して、公正な到着注文ポリシーを取得できます。インターフェイスReadWriteLockは、プロデューサーとコンシューマーの動作を取得します。クラスReentrantReadWriteLockは、両方の機能を組み合わせたものです。

または

このパターンがすでに実装されているArrayBlockingQueueを使用する必要があります。

int capacity = 10;
boolean fair = true;
new ArrayBlockingQueue(capacity, fair);
于 2012-05-21T11:52:00.773 に答える
2

notify()の javadoc から:

通知するスレッドの選択は「任意であり、実装の裁量で行われます」

これは、スレッドをウェイクするための「公平な」アルゴリズム (コンピューター サイエンスと並列処理で使用される用語) ではないことはほぼ確実です。同じスレッドが立て続けに 2 回起動される可能性は十分にあります。また、偽の通知も可能であることに注意してください。

一般に、これを自分で行う代わりにBlockingQueue実装を使用することを提案するコメントに同意します。

于 2012-05-21T11:21:33.943 に答える