1. 程式人生 > >Object的wait、notify和notifyAll

Object的wait、notify和notifyAll

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.IllegalMonitorStateException異常。

至於原因,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."<init>":()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行可以看到monitorenter和monitorexit兩條指令,monitorenter是嘗試獲取monitor,如果成功則執行,不成功則阻塞等待。monitorexit是釋放monitor。

那麼如何擁有該物件的監視器呢?

jdk原始碼notify方法中列舉了三種方法:

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 修飾的程式碼會使用monitorenter指令獲取監視器,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不起作用= =、

&n