Java notifyとnotifyAllの違いは同じです

3333 ワード

よく上をぶらぶらしていますが、javaではnotifyとnotifyAllについて、次のような言い方をする人がよくいます.
notifyは待機中のオブジェクトを1つだけ通知し、notifyAllは待機中のすべてのオブジェクトを通知し、すべてのオブジェクトが実行を続行します.
そして、証明できる例があるようです.上の言い方は、正しいと言ってもいいし、間違っていると言ってもいい.その原因を究明するには、いくつかの重要な点があります.公式の言い方は以下の通りです.
wait,notify,notifyAll:
このメソッドは、このオブジェクトモニタの所有者であるスレッドのみが呼び出されます.スレッドは、このオブジェクトの同期インスタンスメソッドを実行することによって、このオブジェクトモニタの所有者になります.このオブジェクト上で同期するsynchronized文の本文を実行します.クラスタイプのオブジェクトでは、クラスの同期静的メソッドを実行できます.オブジェクトを持つモニタは一度に1つのスレッドしかありません.
以上、javadocから抜粋します.すなわち、呼び出しではオブジェクトモニタ(すなわちロック)を持たなければならない.synchronizedメソッド内で実行する必要があると理解できる.この言葉の暗黙的な意味は、同期ブロックに含まれるコードブロックを継続するには、ロックを再取得する必要があるということである.javadocでは、次のように記述されている.
wait
このメソッドにより、現在のスレッドが作成されます.(Tという)オブジェクトの待機セットに自身を配置し、そのオブジェクト上のすべての同期要件を破棄する.スレッドスケジューリングの目的で、スレッドTは、次の4つのケースのいずれかが発生する前に無効化され、スリープ状態にある.他のスレッドは、このオブジェクトのnotifyメソッドを呼び出し、スレッドTは、たまたま起動されたスレッドとして選択される.他の線プログラムは、このオブジェクトのnotifyAllメソッドを呼び出します.他のスレッドはスレッドTを中断する.ほぼ指定された実際の時間に達しています.ただし、timeoutがゼロの場合、実際の時間は考慮されず、通知が取得されるまでスレッドは待機します.そして、オブジェクトのウエイトセットからスレッドTを削除し、スレッドスケジューリングを再開する.その後、スレッドは、通常の方法で他のスレッドと競合して、オブジェクト上で同期する権利を得る.オブジェクトに対する制御権が取得されると、オブジェクト上の同期宣言はすべて以前の状態に戻ります.これがwaitメソッドを呼び出す場合です.そして、スレッドTはwaitメソッドの呼び出しから返される.したがって,waitメソッドから戻ると,そのオブジェクトとスレッドTの同期状態はwaitメソッドを呼び出す場合と全く同じである.
つまり、notifyAllにとって、すべてのスレッドが通知されるにもかかわらず、取得ロックを再実行する必要があります.しかし、これらのスレッドは競合し、1つのスレッドがロックに成功するだけで、このスレッドが実行されない前に、他のスレッドは待たなければなりません(ただし、ここではnotifyAll通知は必要ありません.notifyAllはすでに行われているので、ロックを取得するだけです)次のようなコードがあり、この現象を再現することができます.
まず、次のように実行可能なスレッドクラスを定義します.

private static final Object obj = new Object();
  static class R implements Runnable {
    int i;
 
    R(int i) {
      this.i = i;
    }
 
    public void run() {
      try {
        synchronized(obj) {
          System.out.println("  -> " + i + "    ");
          obj.wait();
          System.out.println("  -> " + i + "     ");
          Thread.sleep(30000);
        }
      } catch(Exception e) {
        e.printStackTrace();
      }
    }
  }

上記のrunメソッドの内部に注意して、wait()の後、一言印刷して、現在のコードを30秒停止します.sleepメソッドについては、スレッドがモニタの所有権を失わないことを説明します.つまり、ロックはまだ保持されています.
次に、次のようにmainメソッドを定義してスレッドを実行します.

Thread[] rs = new Thread[10];
    for(int i = 0;i < 10;i++) {
      rs[i] = new Thread(new R(i));
    }
    for(Thread r : rs) {
      r.start();
    }
 
    Thread.sleep(5000);
    synchronized(obj) {
      obj.notifyAll();
    }

10個のスレッドを定義し、すべて実行します.waitがあるため、10個のスレッドは「実行開始」を印刷して待機します.次にmainメソッドはnotifyAllを呼び出します.出力は次のようになります.
スレッド->0待機中スレッド->4待機中スレッド->5待機中スレッド->3待機中スレッド->2待機中スレッド->1待機中スレッド->6待機中スレッド->7待機中スレッド->8待機中スレッド->9待機中スレッド->9実行中...30秒以内に他の出力はありません
上記の出力では、waitの後、1つのスレッドだけが「実行中」文を出力し、しばらく(ここでは30秒)、他の出力はありません.すなわち、現在のコードがロックを持っている間は、他のスレッドは出力されません.
最後の結論は、waitのスレッドによって、実行を継続するには、2つの条件を満たさなければならないということです.
他のスレッドnotifyまたはnotifyAllによって、現在のスレッドが通知されました.
他のスレッドとのロック競合により,ロックに成功した2つの条件が1つ欠けている.実装の面では、notifyとnotifyAllは同じ効果を達成し、スレッドが実行されます.しかしnotifyAllは免除され、スレッドの実行が完了したら他のスレッドに通知する必要があります.通知されたからです.いつnotifyを使うか、いつnotifyAllを使うかは、実際の状況次第です.
以上、Java notifyとNotifyAllの資料を整理しました.