執行緒中的wait() 與 鎖的關係
我們先看一段程式碼:
/** * 計算輸出其他執行緒鎖計算的資料 * */ public class ThreadA { public static void main(String[] args) throws InterruptedException{ ThreadB b = new ThreadB(); //啟動計算執行緒 b.start(); //執行緒A擁有b物件上的鎖。執行緒為了呼叫wait()或notify()方法,該執行緒必須是那個物件鎖的擁有者 synchronized (b) { System.out.println("等待物件b完成計算。。。"); //當前執行緒A等待 b.wait(); System.out.println("b物件計算的總和是:" + b.total); } } } /** * 計算1+2+3 ... +100的和 * */ class ThreadB extends Thread { int total; public void run() { synchronized (this) { for (int i = 0; i < 101; i++) { total += i; } //(完成計算了)喚醒在此物件監視器上等待的單個執行緒,在本例中執行緒A被喚醒 notify(); System.out.println("計算完成"); } } }
執行結果:
等待物件b完成計算。。。
計算完成
b物件計算的總和是:5050
如果我們將b.wait()去掉呢?結果如下:
等待物件b完成計算。。。
b物件計算的總和是:0
計算完成
上述的結果表明,當去掉b.wait()時,新啟動的執行緒ThreadB與主執行緒ThreadA是各自執行的,沒有執行緒等待的現象。
我們想要的效果是,當執行緒ThreadB完成計算之後,再去取計算後的結果。所以使用了b.wait()來讓主執行緒等待。
那為什麼是使用b.wait(),而不是Thread.currentThread.wait(),或者其他的呢?
如果我們將b.wait()替換成Thread.currentThread.wait(),將會得到如下的結果:
Exception in thread "main" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:485)
at pa.com.thread.ThreadA.main(ThreadA.java:18)
等待物件b完成計算。。。
計算完成
替換的程式碼Thread.currentThread.wait()好像理所當然應該如我們預期的正確啊,讓當前執行緒處於等待狀態,讓其他執行緒先執行。
我們忽略了一個很重要的問題:執行緒與鎖是分不開的,執行緒的同步、等待、喚醒都與物件鎖是密不可分的。
執行緒ThreadA持有物件b的鎖,我們要使用這把鎖去讓執行緒釋放鎖,從而讓其他的執行緒能搶到這把鎖。
從我們的程式來分析就是:執行緒ThreadA首先持有鎖物件b,然後呼叫b.wait()將物件鎖釋放,執行緒ThreadB爭搶到物件鎖b,從而執行run()方法中的計算,計算完了之後使用notify()喚醒主執行緒ThreadA,ThreadA得以繼續執行,從而得到了我們預期的效果。
(之所以ThreadB的物件鎖也是b,是因為synchronized(this)中的this指向的就是ThreadB的例項b)
Thread.currentThread.wait()呼叫的是當前執行緒物件(即主執行緒ThreadA)的wait()方法,當前執行緒物件ThreadA是沒有被加鎖的,它只是獲取了物件鎖b。我基本沒有看到過這樣的呼叫,一般使用的是鎖物件的wait(),本例中為b.wait()
順帶講一下wait()與sleep()的區別。
如果我們將b.wait()換成Thread.sleep(1000),則會出現如下的結果:
等待物件b完成計算。。。
b物件計算的總和是:0
計算完成
從執行結果可以看出,Thread.sleep(1000)只是讓主執行緒ThreadA睡眠了1秒鐘,而並沒有釋放物件鎖,所以在主執行緒ThreadA睡眠的過程中,ThreadB拿不到物件鎖,從而不能執行。
所以我們也就得出瞭如下的結論:
wait()方法是讓執行緒釋放物件鎖,讓其他執行緒拿到鎖之後去優先執行,當其他全程喚醒wait()中的執行緒 或者 拿到物件鎖的執行緒都執行完釋放了物件鎖之後,wait()中的執行緒才會再次拿到物件鎖從而執行。
sleep()方法是讓執行緒睡眠,此時並沒有釋放物件鎖,其他想要拿到睡眠執行緒的物件鎖的執行緒也就拿不到相應的物件鎖,從而不能搶在它前面執行。
補:
wait、notify和notifyAll方法是Object類的final native方法。所以這些方法不能被子類重寫,Object類是所有類的超類,因此在程式中有以下三種形式呼叫wait等方法。
wait();//方式1:
this.wait();//方式2:
super.wait();//方式3
void wait()
導致執行緒進入等待狀態,直到它被其他執行緒通過notify()或者notifyAll喚醒。該方法只能在同步方法中呼叫。如果當前執行緒不是鎖的持有者,該方法丟擲一個IllegalMonitorStateException異常。