(三)(1)執行緒間通訊---wait和notify的使用
這篇部落格記錄執行緒間通訊相關api使用以及理解。
首先第一點,我之前的部落格裡的執行緒之間也是通訊的,但是他們的通訊是建立在訪問的是同一個變數上的,相當於是變數、資料層面上的通訊,而下面要講的是執行緒層面上的通訊,這種比前者更加可控。
Wait和notify機制
首先明白為什麼會出現這個機制。
目的:舉個例子,現在有A,B兩個執行緒,A執行緒可以不停的改變i的值,B執行緒再i的值為5時終止。
方法:為了實現這種效果,我們需要在B執行緒的run方法之中新增while迴圈,不停的進行檢測i值是否為5,為5則丟擲異常停止或者使用stop,interrupt等。
問題:檢測i的值是否為5這個操作,我們稱之為輪詢,這裡的肯定是很耗時很少的,那麼就會執行很多次,但是其中有一些檢測是沒有必要的,浪費了cpu的資源。
於是乎,就產生了等待/喚醒機制,為了解決cpu資源的浪費,以及讓程式更加可控。
先從字面上簡單理解一下:當一個執行緒執行某個操作但是不滿足條件時,先讓它等候著,直到條件滿足了,再將它喚醒。當然喚醒就是說接著執行相應的操作。
wait方法:
讓當前執行緒進行等待,將其加入到預執行隊列當中,直到終止或者被喚醒為止,這裡的預執行佇列就是指處於和其他執行緒一起競爭獲得該鎖的狀態。並且wait會釋放當前的鎖,這也就是說沒有鎖你是不能呼叫該方法的。
notify:
將處於wait狀態,且競爭的鎖和呼叫notify方法的執行緒持有的鎖相同的執行緒喚醒,這個喚醒是隨機的,相當於在預執行隊列當中隨機喚醒一個執行緒。不過注意notify喚醒並不會立即喚醒,而是將當前同步程式碼塊之中的程式碼執行結束之後再去喚醒,相當於不會釋放鎖。
notifyAll:顧名思義,喚醒依賴於當前鎖所有處於wait的執行緒。
下面通過一個簡單的例子來驗證上述結論,就是之前的那個例子,A執行緒列表元素不為5時wait,B執行緒負責為5時notify:
MyList.java:
package 第三章_wait_join; import java.util.ArrayList; import java.util.List; public class MyList { private static List<String> list = new ArrayList<String>(); public static void add(){ list.add("##"); } public static int getSize(){ return list.size(); } }
ThreadA.java
package 第三章_wait_join; public class ThreadA extends Thread{ private String lock; public ThreadA(String lock){ this.lock=lock; } @Override public void run(){ try{ synchronized(lock){ if(MyList.getSize()!=5){ //不為5則wait System.out.println("等待開始..."); lock.wait(); System.out.println("等待結束..."); } } }catch (InterruptedException e){ e.printStackTrace(); } } }
ThreadB.java
package 第三章_wait_join; public class ThreadB extends Thread{ private String lock; public ThreadB(String lock){ this.lock=lock; } @Override public void run(){ try{ synchronized(lock){ for(int i=0;i<10;i++){ MyList.add(); if(MyList.getSize()==5){ System.out.println("發出通知"); lock.notify(); } System.out.println("添加了"+(i+1)+"個元素"); Thread.sleep(10); } } }catch (InterruptedException e){ e.printStackTrace(); } } }
test.java:
package 第三章_wait_join; public class test { public static void main(String[] args){ try { ThreadA A = new ThreadA("lock"); ThreadB B = new ThreadB("lock"); A.start(); Thread.sleep(50); B.start(); }catch (InterruptedException e){ e.printStackTrace(); } } }
執行結果:
可以看出來,A執行緒開始等待之後就釋放lock鎖,B執行緒獲取到了該鎖,執行程式碼,添加了5個元素時,發出了通知,但是發出通知之後,它沒有釋放鎖,而是將同步程式碼塊執行完,然後再釋放鎖,A執行緒獲取到,執行wait下面的程式碼。
說明兩點:
1.另外和之前一樣,如果一個執行緒已經處於阻塞狀態了,那麼就不能再呼叫其他會產生阻塞的方法,比如呼叫了wait就不能呼叫interrupt,suspend,否則會產生異常,你無法阻塞一個已經被阻塞的執行緒。
2.前面的wait都是沒有引數的,wait(long)就是說在long長時間之內,如果沒有被喚醒,那麼就自動喚醒該執行緒,很好理解,
通知過早
那麼wait,notify肯定也是有一定順序的,你不能還沒有wait就notify,那麼是不會notify任何執行緒的,這也叫做通知過早。看下面的例子:
更改之前的test.java
package 第三章_wait_join; public class test { public static void main(String[] args){ try { ThreadA A = new ThreadA("lock"); ThreadB B = new ThreadB("lock"); B.start(); Thread.sleep(1000); A.start(); }catch (InterruptedException e){ e.printStackTrace(); } } }
執行結果:
可以看到雖然發出了通知,但是這個等待永遠不會結束,因為你在發出通知的時候執行緒還沒有處於阻塞狀態,而是處於就緒狀態,notify並不會喚醒任何執行緒。
&n