Objectのwait、notifyとnotifyAll

17575 ワード

Obectのwait、notifyとnotifyAllはObjectが提供する同期方法で、つまりすべての対象が生まれて持ってくる方法です。javaをやるのはこのいくつかの方法を知らないことはないと思います。彼は一体どうやって使うのですか?ここに自分の理解を記録してください。
まず一番簡単な例を紹介します。
 1 public class SynchronizedTest {
 2     public static void main(String[] args) throws Exception {
 3         Thread mt = new Thread(){
 4             @Override
 5             public void run() {
 6                 synchronized (this) {
 7                     System.out.println("     ");
 8                     try {
 9                         this.wait();
10                     } catch (InterruptedException e) {
11                         e.printStackTrace();
12                     }
13                     System.out.println("     ");
14                 }
15             }
16         };
17         mt.start();
18         Thread.sleep(500);
19         synchronized (mt) {
20             mt.notify();
21         }
22     }
23 }
実行結果:
     
     
上記の例では、waitとnotify方法は、いずれもsynchronizedコード体で実行されていますが、synchronizedで修飾されていない場合は、直接使用するとjava.lang.IllagalMonitontacteExceptionが例外となります。
理由については、jdkソースwait方法の説明は以下の通りです。
* The current thread must own this object's monitor. The thread
* releases ownership of this monitor and waits until another thread
* notifies threads waiting on this object's monitor to wake up
* either through a call to the {@code notify} method or the
* {@code notifyAll} method. The thread then waits until it can
* re-obtain ownership of the monitor and resumes execution.
翻訳してください:
              。                 ,           notify   ,  notifyAll                     。                           。
まとめてみますと、waitなどの一連の方法を使うには、現在の対象のモニター(多くのところではモニタロックと呼ばれています)が必要です。
対象のモニターは何ですか?
簡単に言えば、モニタはjavaオブジェクトが同期動作を実現するための仕組みであり、javapを使って上記の例のスレッド部分の逆コンパイル命令を確認する。
 1 final class com.xxx.SynchronizedTest$1 extends java.lang.Thread {
 2   com.xxx.SynchronizedTest$1();
 3     Code:
 4        0: aload_0
 5        1: invokespecial #1                  // Method java/lang/Thread."":()V
 6        4: return
 7 
 8   public void run();
 9     Code:
10        0: aload_0
11        1: dup
12        2: astore_1
13        3: monitorenter
14        4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
15        7: ldc           #3                  // String      
16        9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
17       12: aload_0
18       13: invokevirtual #5                  // Method java/lang/Object.wait:()V
19       16: goto          24
20       19: astore_2
21       20: aload_2
22       21: invokevirtual #7                  // Method java/lang/InterruptedException.printStackTrace:()V
23       24: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
24       27: ldc           #8                  // String      
25       29: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
26       32: aload_1
27       33: monitorexit
28       34: goto          42
29       37: astore_3
30       38: aload_1
31       39: monitorexit
32       40: aload_3
33       41: athrow
34       42: return
35     Exception table:
36        from    to  target type
37           12    16    19   Class java/lang/InterruptedException
38            4    34    37   any
39           37    40    37   any
40 }
13行と27行はmonitonterとmonit orexitの二つのコマンドを見ることができます。monitonterはmonitorを獲得しようと試みています。成功すれば実行し、成功しなければブロッキングします。monitoshotはリリースmonitorです。
このオブジェクトのモニターはどうやって持ちますか?
jdkソースのnotify方法には3つの方法が挙げられています。
By executing a synchronized instance method of that object.
By executing the body of a {@code synchronized} statement
  that synchronizes on the object.
For objects of type {@code Class,} by executing a
  synchronized static method of that class.
翻訳は大体
              。 
               synchronized       。 
   Class      ,               。 
私が理解しているのは、synchronizedによって修正された方法またはコードブロック(コードブロックであれば、同じ対象をsynchronizedのパラメータとして使用する必要があります。目的は同じモニタを取得するためです。)上の逆コンパイルスレッドの匿名内部コマンドに合わせて、synchronizedを使って修正されたコードはモニタを取得するために、waitとnotifyはモニタを獲得しなければなりません。
文章の開始例に対するエラーの例を挙げます。
 
エラーの例1
 1 public class SynchronizedTest {
 2     public static void main(String[] args) throws Exception {
 3         Thread mt = new Thread(){
 4             @Override
 5             public void run() {
 6                 synchronized (this) {
 7                     System.out.println("     ");
 8                     try {
 9                         this.wait();
10                     } catch (InterruptedException e) {
11                         e.printStackTrace();
12                     }
13                     System.out.println("     ");
14                 }
15             }
16         };
17         mt.start();
18         Thread.sleep(500);
19 //        synchronized (mt) {
20             mt.notify();
21 //        }
22     }
23 }
実行結果:
     
Exception in thread "main" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at xxx
理由:
notify方法はモニタを獲得していません。
 
エラーの例2:
 
 1 public class SynchronizedTest {
 2     public static void main(String[] args) throws Exception {
 3         Thread mt = new Thread(new Runnable() {
 4             @Override
 5             public void run() {
 6                 synchronized (this) {
 7                     System.out.println("     ");
 8                     try {
 9                         this.wait();
10                     } catch (InterruptedException e) {
11                         e.printStackTrace();
12                     }
13                     System.out.println("     ");
14                 }
15             }
16         });
17         mt.start();
18         Thread.sleep(500);
19         synchronized (mt) {
20             mt.notify();
21         }
22     }
23 }
 
実行結果:
     
( wait 。。。
理由:
この例は私の最初の書き方です。ここに置くのはちょっと合わないです。彼はwaitとnotifyの間違いではなく、Runnableを間違えて使ったためです。結局は置くことにします。誰かがすぐに分かりにくくなるのを防ぐためです。
例ではnew Runnableを用いて匿名の内部クラスを作成し、構造パラメータとしてnew Threadに伝達し、構造の対象mtと匿名の内部類のthisは同一のオブジェクトではない。なので、notifyが機能しなくなりました。