系列文章傳送門:java
Java多線程學習(二)synchronized關鍵字(1)程序員
java多線程學習(二)synchronized關鍵字(2) github
Java多線程學習(四)等待/通知(wait/notify)機制編程
系列文章將被優先更新於微信公衆號<font color="red">「Java面試通關手冊」</font>,歡迎廣大Java程序員和愛好技術的人員關注。ide
本節思惟導圖:
思惟導圖源文件+思惟導圖軟件關注微信公衆號:「Java面試通關手冊」 回覆關鍵字:「Java多線程」 免費領取。
當兩個線程之間存在<font color="red">生產和消費者關係</font>,也就是說<font color="red">第一個線程(生產者)作相應的操做而後第二個線程(消費者)感知到了變化又進行相應的操做</font>。好比像下面的whie語句同樣,假設這個value值就是第一個線程操做的結果,doSomething()是第二個線程要作的事,當知足條件value=desire後才執行doSomething()。
可是這裏有個問題就是:第二個語句不停過經過輪詢機制來檢測判斷條件是否成立。<font color="red">若是輪詢時間的間隔過小會浪費CPU資源,輪詢時間的間隔太大,就可能取不到本身想要的數據。因此這裏就須要咱們今天講到的等待/通知(wait/notify)機制來解決這兩個矛盾</font>。
while(value=desire){ doSomething(); }
通俗來說:
等待/通知機制在咱們生活中比比皆是,一個形象的例子就是廚師和服務員之間就存在等待/通知機制。
用專業術語講:
等待/通知機制,是指一個線程A調用了對象O的<font color="red">wait()方法</font>進入<font color="red">等待狀態</font>,而另外一個線程B調用了對象O的<font color="red">notify()/notifyAll()方法</font>,線程A收到通知後退出<font color="red">等待隊列</font>,進入可運行狀態,進而執行後續操做。上訴兩個線程經過對象O來完成交互,而對象上的wait()方法和notify()/notifyAll()方法的關係就如同開關信號同樣,用來完成等待方和通知方之間的交互工做。
方法名稱 | 描述 |
---|---|
notify() | 隨機喚醒等待隊列中等待同一共享資源的 「一個線程」,並使該線程退出等待隊列,進入可運行狀態,也就是notify()方法僅通知「一個線程」 |
notifyAll() | 使全部正在等待隊列中等待同一共享資源的 「所有線程」 退出等待隊列,進入可運行狀態。此時,優先級最高的那個線程最早執行,但也有多是隨機執行,這取決於JVM虛擬機的實現 |
wait() | 使調用該方法的線程釋放共享資源鎖,而後從運行狀態退出,進入等待隊列,直到被再次喚醒 |
wait(long) | 超時等待一段時間,這裏的參數時間是毫秒,也就是等待長達n毫秒,若是沒有通知就超時返回 |
wait(long,int) | 對於超時時間更細力度的控制,能夠達到納秒 |
<font size="2">MyList.java</font>
public class MyList { private static List<String> list = new ArrayList<String>(); public static void add() { list.add("anyString"); } public static int size() { return list.size(); } }
<font size="2">ThreadA.java</font>
public class ThreadA extends Thread { private Object lock; public ThreadA(Object lock) { super(); this.lock = lock; } @Override public void run() { try { synchronized (lock) { if (MyList.size() != 5) { System.out.println("wait begin " + System.currentTimeMillis()); lock.wait(); System.out.println("wait end " + System.currentTimeMillis()); } } } catch (InterruptedException e) { e.printStackTrace(); } } }
<font size="2">ThreadB.java</font>
public class ThreadB extends Thread { private Object lock; public ThreadB(Object lock) { super(); this.lock = lock; } @Override public void run() { try { synchronized (lock) { for (int i = 0; i < 10; i++) { MyList.add(); if (MyList.size() == 5) { lock.notify(); System.out.println("已發出通知!"); } System.out.println("添加了" + (i + 1) + "個元素!"); Thread.sleep(1000); } } } catch (InterruptedException e) { e.printStackTrace(); } } }
<font size="2">Run.java</font>
public class Run { public static void main(String[] args) { try { Object lock = new Object(); ThreadA a = new ThreadA(lock); a.start(); Thread.sleep(50); ThreadB b = new ThreadB(lock); b.start(); } catch (InterruptedException e) { e.printStackTrace(); } } }
<font size="2">運行結果:</font>
從運行結果:"wait end 1521967322359"最後輸出能夠看出,<font color="red">notify()執行後並不會當即釋放鎖。</font>下面咱們會補充介紹這個知識點。
<font color="red">synchronized關鍵字</font>能夠將任何一個Object對象做爲同步對象來看待,而<font color="red">Java爲每一個Object都實現了等待/通知(wait/notify)機制的相關方法</font>,它們必須用在synchronized關鍵字同步的Object的臨界區內。經過調用<font color="red">wait()方法</font>可使處於臨界區內的線程進入<font color="red">等待狀態</font>,同時<font color="red">釋放被同步對象的鎖</font>。而<font color="red">notify()方法</font>能夠喚醒一個因調用wait操做而處於阻塞狀態中的線程,使其進入就緒狀態。被從新喚醒的線程會視圖從新得到臨界區的控制權也就是</font>鎖</font>,並繼續執行wait方法以後的代碼。若是發出notify操做時沒有處於阻塞狀態中的線程,那麼該命令會被忽略。
若是咱們這裏不經過<font color="red">等待/通知(wait/notify)機制</font>實現,而是使用以下的<font color="red">while循環</font>實現的話,咱們上面也講過會有很大的弊端。
while(MyList.size() == 5){ doSomething(); }
上面幾章的學習中咱們已經掌握了與線程有關的大部分API,這些API能夠改變線程對象的狀態。以下圖所示:
(一). 等待阻塞:運行(running)的線程執行o.wait()方法,JVM會把該線程放 入等待隊列(waitting queue)中。
(二). 同步阻塞:運行(running)的線程在獲取對象的同步鎖時,若該同步鎖 被別的線程佔用,則JVM會把該線程放入鎖池(lock pool)中。
(三). 其餘阻塞: 運行(running)的線程執行Thread.sleep(long ms)或t.join()方法,或者發出了I/O請求時,JVM會把該線程置爲阻塞狀態。當sleep()狀態超時join()等待線程終止或者超時、或者I/O處理完畢時,線程從新轉入可運行(runnable)狀態。
備註:
能夠用早起坐地鐵來比喻這個過程:
還沒起牀:sleeping
起牀收拾好了,隨時能夠坐地鐵出發:Runnable
等地鐵來:Waiting
地鐵來了,但要排隊上地鐵:I/O阻塞
上了地鐵,發現暫時沒座位:synchronized阻塞
地鐵上找到座位:Running
到達目的地:Dead
<font color="red">當方法wait()被執行後,鎖自動被釋放,但執行玩notify()方法後,鎖不會自動釋放。必須執行完otify()方法所在的synchronized代碼塊後才釋放。</font>
下面咱們經過代碼驗證一下:
(完整代碼:https://github.com/Snailclimb/threadDemo/tree/master/src/wait_notifyHoldLock)
<font size="2">帶wait方法的synchronized代碼塊</font>
synchronized (lock) { System.out.println("begin wait() ThreadName=" + Thread.currentThread().getName()); lock.wait(); System.out.println(" end wait() ThreadName=" + Thread.currentThread().getName()); }
<font size="2">帶notify方法的synchronized代碼塊</font>
synchronized (lock) { System.out.println("begin notify() ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis()); lock.notify(); Thread.sleep(5000); System.out.println(" end notify() ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis()); }
若是有三個同一個對象實例的線程a,b,c,a線程執行帶wait方法的synchronized代碼塊而後bb線程執行帶notify方法的synchronized代碼塊緊接着c執行帶notify方法的synchronized代碼塊。
<font size="2">運行效果以下:</font>
<font color="red">這也驗證了咱們剛開始的結論:必須執行完notify()方法所在的synchronized代碼塊後才釋放。</font>
<font color="red">當線程呈wait狀態時,對線程對象調用interrupt方法會出現InterrupedException異常。</font>
<font size="2">Service.java</font>
public class Service { public void testMethod(Object lock) { try { synchronized (lock) { System.out.println("begin wait()"); lock.wait(); System.out.println(" end wait()"); } } catch (InterruptedException e) { e.printStackTrace(); System.out.println("出現異常了,由於呈wait狀態的線程被interrupt了!"); } } }
<font size="2">ThreadA.java</font>
public class ThreadA extends Thread { private Object lock; public ThreadA(Object lock) { super(); this.lock = lock; } @Override public void run() { Service service = new Service(); service.testMethod(lock); } }
<font size="2">Test.java</font>
public class Test { public static void main(String[] args) { try { Object lock = new Object(); ThreadA a = new ThreadA(lock); a.start(); Thread.sleep(5000); a.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } }
<font size="2">運行結果:</font>
參考:
《Java多線程編程核心技術》
《Java併發編程的藝術》
若是你以爲博主的文章不錯,歡迎轉發點贊。你能從中學到知識就是我最大的幸運。
歡迎關注個人微信公衆號:「Java面試通關手冊」(分享各類Java學習資源,面試題,以及企業級Java實戰項目回覆關鍵字免費領取)。另外我建立了一個Java學習交流羣(羣號:174594747),歡迎你們加入一塊兒學習,這裏更有面試,學習視頻等資源的分享。