wait()同期されたブロックの外部で呼び出すことが可能で、そのセマンティクスを保持する-呼び出し元のスレッドを一時停止する可能性がある場合、どのような損害が考えられますか?
wait()同期ブロックの外で呼び出すことができる場合に発生する問題を具体例で説明しましょう。
ブロッキングキューを実装するとします(知っていますが、APIには既に1つあります)。
最初の試行(同期なし)は、以下の行に沿って何かを見ることができます
class BlockingQueue {
Queue<String> buffer = new LinkedList<String>();
public void give(String data) {
buffer.add(data);
notify(); // Since someone may be waiting in take!
}
public String take() throws InterruptedException {
while (buffer.isEmpty()) // don't use "if" due to spurious wakeups.
wait();
return buffer.remove();
}
}
これは潜在的に起こり得ることです:
コンシューマスレッドはを呼び出しtake()、それを確認しbuffer.isEmpty()ます。
コンシューマースレッドがを呼び出す前wait()に、プロデューサースレッドがやって来て、フルgive()、つまり、buffer.add(data); notify();
消費者のスレッドは現在、呼び出しますwait()(そして欠場notify()だけ呼ばれていたことを)。
運が悪いと、プロデューサースレッドはgive()、コンシューマースレッドが起動しないため、デッドロックが発生するため、これ以上生成しません。
問題を理解したら、解決策は明らかです。を使用synchronizedしてnotifyとの間isEmptyで呼び出されないようにしwaitます。
詳細には触れませんが、この同期の問題は一般的なものです。Michael Borgwardtが指摘しているように、待機/通知はすべてスレッド間の通信に関するものであるため、常に上記のような競合状態が発生します。これが、「同期内でのみ待機する」ルールが適用される理由です。
@Willieによって投稿されたリンクからの段落は、それを非常によく要約しています:
ウェイターとノーティファイアが述語の状態について合意することの絶対的な保証が必要です。ウェイターは、スリープ状態になる前の少し前に、述部の状態をチェックしますが、スリープ状態になるときに述部が真であるかどうかに依存します。これら2つのイベントの間に脆弱性の期間があり、プログラムを破壊する可能性があります。
プロデューサとコンシューマが同意する必要がある述語は、上記の例にありbuffer.isEmpty()ます。また、待機と通知がsynchronizedブロックで実行されるようにすることで、合意が解決されます。
この投稿は、ここの記事として書き直されました:Java:同期ブロックで待機を呼び出す必要がある理由