wait() 一定需要notify()喚醒嗎?20120810
寫這篇文章是源於論壇裡的一個文章, "線程同步問題,分析下程式運行結果" 其地址為:
http://topic.csdn.net/u/20120808/17/f1742060-7501-430b-b509-a492eef535f5.html.
當時按照一般的wait(),notify()思想做瞭解釋,但樓主質疑,認為解釋的與實際情況不一致,再後來測試時,也確實發現樓主的質疑是對的。 雖然樓主已經結貼了,但我覺得還是應該弄清楚,為什麼會有這樣兩種結果。
先來看一下文章的代碼:
package test;public class ThreadA{public static void main(String[] args){ThreadB b = new ThreadB();Threadc c = new Threadc();c.setName("c線程");b.setName("b線程");c.start();System.out.println(Thread.currentThread().getName()+"is start....");synchronized(c){try{System.out.println("waiting for b1 to complete....");c.wait();System.out.println("Completed.now back to"+Thread.currentThread().getName());b.start();}catch(InterruptedException e){}}}}class ThreadB extends Thread{int total;public void run(){synchronized(this){System.out.println(Thread.currentThread().getName()+"is running..");for(int i=0;i<10;i++){total +=i;}System.out.println("total is"+total);}}}class Threadc extends Thread{int sum=1;public void run(){synchronized(this){System.out.println(Thread.currentThread().getName()+"is running..");for(int i=1;i<10;i++){sum *=i;}System.out.println("sum is"+sum);notify();}}}
代碼啟動並執行結果有2種:
1 程式完全運行完,正常退出。
main is start....
c線程 is running..
sum is 362880
waiting for b1 to complete....
Completed.now back tomain
b線程 is running..
total is 45
2 程式停在那“不動”了,不能正常退出。
main is start....
c線程 is running..
sum is 362880
waiting for b1 to complete....
當時我的解釋:
“這兩種結果產生的原因是以對象c為同步對象的兩段代碼塊,誰先啟動並執行問題。
第一種情況,主線程優先運行到synchonized(c)標記的同步代碼塊,這時c線程synchronized(this)標記的代碼塊就暫時無法運行。主線程運行到c.wait()時,主線程釋放同步對象的鎖,進入阻塞狀態,c線程會由阻塞狀態變為運行狀態。c線程運行完後,通過其 notify(),使主線程由阻塞狀態進入運行狀態,正常執行完畢。
第二種情況,c線程優先運行到synchronized(this)標記的代碼塊,這時主線程因為得不到同步對象鎖而阻塞。當c線程運行結束後,釋放同步對象鎖,這時主線程由阻塞進入運行狀態,當運行到c.wait()時,進入阻塞狀態,因沒有程式運行notify(),所以一直阻塞下去了。
”
當經樓主質疑後,經過大量測試,發現上面的解釋是不正確的。正象樓主質疑的一樣,測試發現每次程式運行都是c線程先進入同步程式碼片段,而主線程的同步程式碼片段是在c線程退出同步程式碼片段後進入的。
關鍵問題就在這了: 既然每次都是c線程進入同步程式碼片段,那主線程的c.wait()將一直停在那等才對呢,根本就不能有第一種情況出現!因為c線程的notify()已經運行完了!沒有誰再會喚醒這個wait了。
但問題是有時(而且根據測試是多數時候)程式繼續運行,而且正常退出了。
真是不可思議,與自己以前看到的,學到的知識發生衝突了!
為了進一步證實,程式的wait可以不需要 noyify()喚醒,我乾脆在Threadc類裡,把notify()注釋掉了,編譯運行(都是多次運行),發現結果和原來一樣:有時正常退出,有時停那不動。
這時,就能得出結論:c.wait()運行後,不需要c.notify()能夠喚醒!
問題又來了,既然不需要喚醒,那應該是每次都能正常退出啊?為什麼有時停那了呢?
繼續測試,在主線程的同步程式碼片段裡,在c.wait()前加一句:
System.out.println("線程c的狀態是:"+c.isAlive());
編譯運行後,發現所有正常結束的時候,上述語句輸出的都是 true. 而不能正常結束的時候,輸出的都是false!
因此說明線程正常結束後,會使以這個線程對象啟動並執行wait()等待,退出等待狀態!而如果在運行wait()之前,線程已經結束了,則這個wait就沒有程式喚醒了。
看看thread類的原碼,證明了我上述觀點!
原碼裡的join()方法,實際上就是啟動並執行 wait(). 需要運行join的線程運行join方法,實際上是在此線程上調用了需要加入的線程對象的wait()方法,加入的線程運行完後,自然從wait退出了。
到此,就得出了我的結論:
1 線程對象的wait()方法運行後,可以不用其notify()方法退出,會線上程結束後,自動結束。
2 線程間的等待喚醒機制,最好不要用線程對象做同步鎖!