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