職場の誰かが、シンクロナイズド内で待機をラップする必要がある理由を尋ねました。
正直なところ、理由がわかりません。 javadocsが言っていることを理解しています-スレッドはオブジェクトのモニターの所有者である必要がありますが、なぜですか?どのような問題を防ぎますか? (そして、それが実際に必要な場合、waitメソッドがモニター自体を取得できないのはなぜですか?)
私はかなり詳細な理由またはおそらく記事への参照を探しています。クイックグーグルで見つけることができませんでした。
ああ、また、thread.sleepはどのように比較されますか?
編集:素晴らしい答えのセット-何が起こっているのかを理解するのにすべてが役立ったので、複数選択できたらいいのにと思います。
Object.wait()を呼び出すときにオブジェクトがオブジェクトモニターを所有していない場合、モニターが解放されるまで、オブジェクトにアクセスして通知リスナーを設定することはできません。代わりに、同期されたオブジェクトのメソッドにアクセスしようとするスレッドとして扱われます。
言い換えれば、次の間に違いはありません。
public void doStuffOnThisObject()
および次の方法:
public void wait()
オブジェクトモニターが解放されるまで、両方のメソッドはブロックされます。これは、オブジェクトの状態が複数のスレッドによって更新されるのを防ぐためのJavaの機能です。これは、wait()メソッドに意図しない結果をもたらすだけです。
おそらく、wait()メソッドは同期されていません。これは、スレッドがオブジェクトに対して複数のロックを持っている状況を作り出す可能性があるためです。 (これについての詳細は、 Java言語仕様/ロック を参照してください。)wait()メソッドは1つのロックしか元に戻さないため、複数のロックが問題になります。メソッドが同期されている場合は、メソッドのロックのみが取り消され、潜在的な外部ロックは取り消されたままになることが保証されます。これにより、コードにデッドロック状態が発生します。
Thread.sleep()に関する質問に答えるために、Thread.sleep()は、待機している条件が満たされていることを保証しません。 Object.wait()とObject.notify()を使用すると、プログラマーは手動でブロッキングを実装できます。条件が満たされたという通知が送信されると、スレッドはブロックを解除します。例えばディスクからの読み取りが終了し、スレッドでデータを処理できるようになりました。 Thread.sleep()では、条件が満たされている場合はプログラマーがポーリングし、満たされていない場合はフォールバックしてスリープ状態にする必要があります。
ここにはすでにたくさんの良い答えがあります。しかし、ここで言及したいのは、wait()を使用するときに他にやらなければならないことは、私の経験では実際に発生する偽のウェイクアップが発生した場合に備えて、待機している条件に応じてループで実行することです。
他のスレッドが条件をtrueに変更して通知するのを待つには、次のようにします。
synchronized(o) {
while(! checkCondition()) {
o.wait();
}
}
もちろん、最近では、新しいConditionオブジェクトを使用することをお勧めします。これは、より明確で機能が多いためです(ロックごとに複数の条件を許可する、待機キューの長さを確認できる、より柔軟なスケジュール/割り込みなど)。
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
try {
while (! checkCondition()) {
condition.await();
}
} finally {
lock.unlock();
}
}
Wait()の目的はモニターを解放し、他のスレッドがモニターを取得して独自の処理を実行できるようにすることであるため、モニターを所有する必要があります。これらのメソッド(待機/通知)の目的は、いくつかの機能を実行するために相互に必要な2つのスレッド間で同期されたコードブロックへのアクセスを調整することです。データ構造へのアクセスがスレッドセーフであることを確認するだけでなく、複数のスレッド間でイベントを調整することも重要です。
典型的な例は、1つのスレッドがデータをキューにプッシュし、別のスレッドがデータを消費するプロデューサー/コンシューマーの場合です。消費スレッドは常にモニターがキューにアクセスする必要がありますが、キューが空になるとモニターを解放します。プロデューサースレッドは、コンシューマーが処理しなくなったときにのみ、スレッドへの書き込みにアクセスできます。より多くのデータをキューにプッシュすると、コンシューマースレッドに通知するため、モニターを取り戻し、キューに再度アクセスできます。
モニターをあきらめるのを待つので、あきらめるにはモニターが必要です。 Notifyにはモニターも必要です。
これを行う主な理由は、wait()から戻ったときにモニターがあることを確認するためです。通常、共有リソースを保護するために待機/通知プロトコルを使用しており、安全に保護する必要があります。待機が戻ったらタッチします。 notifyと同じです-通常は何かを変更してからnotify()を呼び出します-モニターを作成し、変更を加え、notify()を呼び出します。
このような関数を作成した場合:
public void synchWait() {
syncronized { wait(); }
}
待機が戻ったとき、モニターはありません。モニターは入手できますが、次に入手できない可能性があります。
これが、制限が実際に要件である理由についての私の理解です。これは、ミューテックスと条件変数を組み合わせてしばらく前に作成したC++モニターの実装に基づいています。
mutex + condition_variable = monitorシステムでは、 wait 呼び出しにより、条件変数が待機状態に設定され、ミューテックスが解放されます。条件変数は共有状態であるため、待機したいスレッドと通知したいスレッドの間の競合状態を回避するためにロックする必要があります。状態をロックするためにさらに別のミューテックスを導入する代わりに、既存のミューテックスが使用されます。 Javaでは、待機中のスレッドがモニターを所有している場合、ミューテックスは正しくロックされます。
ほとんどの場合、キューが空であるなどの条件がある場合に待機が行われます。
If(queue is empty)
queue.wait();
キューが空であると仮定しましょう。現在のスレッドがキューをチェックした後にプリエンプトした場合、別のスレッドがキューにいくつかの要素を追加すると、現在のスレッドはそれを認識せず、待機状態になります。それは間違っている。だから私たちは次のようなものを持っている必要があります
Synchornized(queue)
{
if(queue is empty)
queue.wait();
}
ここで、同期された状態で待機させた場合はどうなるかを考えてみましょう。コメントの1つですでに述べたように、それは1つのロックのみを解放します。つまり、上記のコードでwait()が同期された場合、1つのロックのみが解放されます。現在のスレッドがキューのロックで待機することを意味します。