297

Object.wait()を呼び出すには、この呼び出しを同期ブロックに配置する必要があることは誰もが知っています。そうしないと、IllegalMonitorStateExceptionがスローされます。しかし、この制限を行う理由は何ですか?モニターを解放することは知っていwait()ますが、特定のブロックを同期させてモニターを明示的に取得してから、を呼び出してモニターを解放する必要があるのはなぜwait()ですか?

wait()同期されたブロックの外部を呼び出し、そのセマンティクスを保持して、呼び出し元のスレッドを一時停止することができた場合、潜在的な損害は何ですか?

4

10 に答える 10

303

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();
    }
}

これは潜在的に起こり得ることです:

  1. コンシューマースレッドが呼び出しtake()て、それを確認しbuffer.isEmpty()ます。

  2. コンシューマースレッドが呼び出しを続ける前にwait()、プロデューサースレッドがやって来て、フルを呼び出しますgive()buffer.add(data); notify();

  3. コンシューマースレッドはこれで呼び出しますwait()(そして、呼び出されたばかりのスレッドを見逃します)。notify()

  4. give()運が悪ければ、コンシューマースレッドがウェイクアップしないという事実の結果として、プロデューサースレッドはそれ以上生成せず、デッドロックが発生します。

問題を理解すると、解決策は明らかです。との間で呼び出されないsynchronizedようにするために使用します。notifyisEmptywait

詳細に立ち入ることなく:この同期の問題は普遍的です。Michael Borgwardtが指摘しているように、待機/通知はすべてスレッド間の通信に関するものであるため、常に上記と同様の競合状態になります。これが、「同期内でのみ待機」ルールが適用される理由です。


@Willieによって投稿されたリンクの段落は、それを非常によく要約しています。

ウェイターと通知者が述語の状態について合意するという絶対的な保証が必要です。ウェイターは、スリープ状態になる前のある時点で述語の状態をチェックしますが、スリープ状態になるときに述語が真であるかどうかに依存します。これらの2つのイベントの間には脆弱性の期間があり、プログラムを壊す可能性があります。

生産者と消費者が合意する必要がある述語は、上記の例にありbuffer.isEmpty()ます。synchronizedそして、待機と通知がブロックで実行されるようにすることで、合意が解決されます。


この投稿はここの記事として書き直されました:Java:同期ブロックで待機を呼び出さなければならない理由

于 2010-05-06T08:43:23.047 に答える
259

が存在するwait()場合にのみ意味がありますnotify()。したがって、それは常にスレッド間の通信に関するものであり、正しく機能するには同期が必要です。これは暗黙的であるべきだと主張する人もいるかもしれませんが、次の理由から、それは実際には役に立ちません。

意味的には、あなたは決してwait()。満足するためには何らかの条件が必要であり、そうでない場合は、満足するまで待ちます。だからあなたが本当にすることは

if(!condition){
    wait();
}

ただし、条件は別のスレッドによって設定されているため、これを正しく機能させるには、同期が必要です。

スレッドが待機をやめたからといって、探している条件が真であるとは限らないという、さらにいくつかの問題があります。

  • 偽のウェイクアップ(スレッドが通知を受信せずに待機からウェイクアップできることを意味します)、または

  • 条件は設定できますが、3番目のスレッドは、待機中のスレッドがウェイクアップする(そしてモニターを再取得する)までに、条件を再びfalseにします。

これらのケースに対処するために本当に必要なのは、常にこれのバリエーションです。

synchronized(lock){
    while(!condition){
        lock.wait();
    }
}

さらに良いことに、同期プリミティブをまったくいじらないで、java.util.concurrentパッケージで提供されている抽象化を使用してください。

于 2010-05-06T08:22:56.937 に答える
14

@Rollerballは正しいです。が呼び出され、この呼び出しがwait()発生したときにスレッドが何らかの条件が発生するのを待つことができるようwait()に、スレッドはロックを放棄するように強制されます。
何かをあきらめるには、最初にそれを所有する必要があります。スレッドは最初にロックを所有する必要があります。synchronizedしたがって、メソッド/ブロック内で呼び出す必要があります。

synchronizedはい、メソッド/ブロック内の状態をチェックしなかった場合の潜在的な損害/矛盾に関する上記のすべての回答に同意します。ただし、@ shrini1000が指摘しているように、wait()同期されたブロック内で呼び出すだけでは、この不整合の発生を回避することはできません。

これがいい読み物です。

于 2013-08-14T07:21:02.007 に答える
4

以前に同期しない場合に発生する可能性のある問題wait()は次のとおりです。

  1. 1番目のスレッドがmakeChangeOnX()while条件に入ってチェックし、それがtruex.metCondition()returns false、means x.conditionis false)である場合、それはその中に入るでしょう。次に、メソッドの直前にwait()、別のスレッドがに移動し、toとをsetConditionToTrue()設定します。x.conditiontruenotifyAll()
  2. その後、最初のスレッドが彼のメソッドに入ります(少し前に起こっwait()たことの影響を受けません)。notifyAll()この場合、最初のスレッドは別のスレッドが実行するのを待機し続けますがsetConditionToTrue()、それは二度と起こらない可能性があります。

ただしsynchronized、オブジェクトの状態を変更するメソッドの前に置くと、これは起こりません。

class A {

    private Object X;

    makeChangeOnX(){
        while (! x.getCondition()){
            wait();
            }
        // Do the change
    }

    setConditionToTrue(){
        x.condition = true; 
        notifyAll();

    }
    setConditionToFalse(){
        x.condition = false;
        notifyAll();
    }
    bool getCondition(){
        return x.condition;
    }
}
于 2016-02-26T14:24:07.727 に答える
3

スレッド間通信には、wait()、notify()、notifyAll()メソッドが使用されることは誰もが知っています。信号の欠落や誤ったウェイクアップの問題を取り除くために、待機中のスレッドは常に特定の条件で待機します。例えば-

boolean wasNotified = false;
while(!wasNotified) {
    wait();
}

次に、通知スレッドセットのwasNotified変数をtrueに設定し、通知します。

すべてのスレッドにはローカルキャッシュがあるため、すべての変更は最初にそこに書き込まれ、次にメインメモリに徐々に昇格されます。

これらのメソッドが同期ブロック内で呼び出されなかった場合、wasNotified変数はメインメモリにフラッシュされず、スレッドのローカルキャッシュに存在するため、待機中のスレッドは通知スレッドによってリセットされても信号を待機し続けます。

これらのタイプの問題を修正するために、これらのメソッドは常に同期ブロック内で呼び出されます。これにより、同期ブロックが開始されると、すべてがメインメモリから読み取られ、同期ブロックを終了する前にメインメモリにフラッシュされます。

synchronized(monitor) {
    boolean wasNotified = false;
    while(!wasNotified) {
        wait();
    }
}

ありがとう、それが明らかになることを願っています。

于 2017-12-01T16:47:58.093 に答える
2

これは基本的にハードウェアアーキテクチャ(つまりRAMキャッシュ)と関係があります。

synchronizedまたはと一緒に使用しない場合wait()、モニターがブロックに入るのを待つ代わりにnotify()、別のスレッドが同じブロックに入る可能性があります。さらに、たとえば同期ブロックなしで配列にアクセスする場合、別のスレッドはその変更を認識しない可能性があります...実際には、別のスレッドは、xレベルキャッシュに配列のコピーがすでにある場合、その変更を認識しません( CPUコアを処理するスレッドの別名1st/2nd / 3rd-level caches)。

ただし、同期されたブロックはメダルの片側にすぎません。同期されていないコンテキストから同期されたコンテキスト内のオブジェクトに実際にアクセスする場合、オブジェクトは同期されたブロック内でも同期されません。これは、オブジェクトが独自のコピーを保持しているためです。キャッシュ内のオブジェクト。私はこの問題についてここに書きました:https ://stackoverflow.com/a/21462631そしてロックが非最終オブジェクトを保持しているとき、オブジェクトの参照はまだ別のスレッドによって変更できますか?

さらに、xレベルのキャッシュがほとんどの再現不可能なランタイムエラーの原因であると私は確信しています。これは、開発者は通常、CPUの動作や、メモリ階層がアプリケーションの実行にどのように影響するかなど、低レベルのことを学習しないためです。http: //en.wikipedia.org/wiki/Memory_hierarchy

プログラミングクラスが最初にメモリ階層とCPUアーキテクチャで始まらない理由は謎のままです。「Helloworld」はここでは役に立ちません。;)

于 2014-01-28T15:36:27.173 に答える
1

ドキュメントによると:

現在のスレッドは、このオブジェクトのモニターを所有している必要があります。スレッドはこのモニターの所有権を解放します。

wait()メソッドは、単にオブジェクトのロックを解除することを意味します。したがって、オブジェクトは同期されたブロック/メソッド内でのみロックされます。スレッドが同期ブロックの外側にある場合、それはロックされていないことを意味します。ロックされていない場合、オブジェクトで何を解放しますか?

于 2019-06-03T07:29:34.747 に答える
1

監視オブジェクト(同期ブロックで使用されるオブジェクト)でのスレッド待機。単一スレッドの全行程でn個の監視オブジェクトが存在する可能性があります。スレッドが同期ブロックの外部で待機している場合、監視オブジェクトはなく、他のスレッドも監視オブジェクトへのアクセスを通知します。したがって、同期ブロックの外部のスレッドは、通知されたことをどのように知るのでしょうか。これは、wait()、notify()、notifyAll()がスレッドクラスではなくオブジェクトクラスにある理由の1つでもあります。

基本的に、監視オブジェクトはすべてのスレッドに共通のリソースであり、監視オブジェクトは同期ブロックでのみ使用できます。

class A {
   int a = 0;
  //something......
  public void add() {
   synchronization(this) {
      //this is your monitoring object and thread has to wait to gain lock on **this**
       }
  }
于 2020-06-21T18:16:49.020 に答える
0

このJavaOracleチュートリアルから直接:

スレッドがd.waitを呼び出すときは、dの固有のロックを所有している必要があります。そうでない場合、エラーがスローされます。同期メソッド内で待機を呼び出すことは、組み込みロックを取得する簡単な方法です。

于 2013-05-27T07:30:47.620 に答える
0

notify()オブジェクトを呼び出すとt、Javaは特定のt.wait()メソッドに通知します。しかし、Javaはどのようにして特定のwaitメソッドを検索して通知しますか。

Javaは、オブジェクトによってロックされた同期されたコードブロックのみを調べますt。Javaは、特定のに通知するためにコード全体を検索することはできませんt.wait()

于 2016-03-10T08:23:53.693 に答える