摘要:運行可運行狀態的線程獲得了時間片,執行程序代碼。阻塞的情況分三種一等待阻塞運行的線程執行方法,會把該線程放入等待隊列中。死亡線程方法執行結束,或者因異常退出了方法,則該線程結束生命周期。死亡的線程不可再次復生。
系列文章傳送門:
Java多線程學習(一)Java多線程入門
Java多線程學習(二)synchronized關鍵字(1)
java多線程學習(二)synchronized關鍵字(2)
Java多線程學習(三)volatile關鍵字
Java多線程學習(四)等待/通知(wait/notify)機制
Java多線程學習(五)線程間通信知識點補充
Java多線程學習(六)Lock鎖的使用
Java多線程學習(七)并發編程中一些問題
系列文章將被優先更新于微信公眾號“Java面試通關手冊”,歡迎廣大Java程序員和愛好技術的人員關注。
本節思維導圖:
思維導圖源文件+思維導圖軟件關注微信公眾號:“Java面試通關手冊” 回復關鍵字:“Java多線程” 免費領取。
一 等待/通知機制介紹 1.1 不使用等待/通知機制當兩個線程之間存在生產和消費者關系,也就是說第一個線程(生產者)做相應的操作然后第二個線程(消費者)感知到了變化又進行相應的操作。比如像下面的whie語句一樣,假設這個value值就是第一個線程操作的結果,doSomething()是第二個線程要做的事,當滿足條件value=desire后才執行doSomething()。
但是這里有個問題就是:第二個語句不停過通過輪詢機制來檢測判斷條件是否成立。如果輪詢時間的間隔太小會浪費CPU資源,輪詢時間的間隔太大,就可能取不到自己想要的數據。所以這里就需要我們今天講到的等待/通知(wait/notify)機制來解決這兩個矛盾。
while(value=desire){ doSomething(); }1.2 什么是等待/通知機制?
通俗來講:
等待/通知機制在我們生活中比比皆是,一個形象的例子就是廚師和服務員之間就存在等待/通知機制。
廚師做完一道菜的時間是不確定的,所以菜到服務員手中的時間是不確定的;
服務員就需要去“等待(wait)”;
廚師把菜做完之后,按一下鈴,這里的按鈴就是“通知(nofity)”;
服務員聽到鈴聲之后就知道菜做好了,他可以去端菜了。
用專業術語講:
等待/通知機制,是指一個線程A調用了對象O的wait()方法進入等待狀態,而另一個線程B調用了對象O的notify()/notifyAll()方法,線程A收到通知后退出等待隊列,進入可運行狀態,進而執行后續操作。上訴兩個線程通過對象O來完成交互,而對象上的wait()方法和notify()/notifyAll()方法的關系就如同開關信號一樣,用來完成等待方和通知方之間的交互工作。
1.3 等待/通知機制的相關方法方法名稱 | 描述 |
---|---|
notify() | 隨機喚醒等待隊列中等待同一共享資源的 “一個線程”,并使該線程退出等待隊列,進入可運行狀態,也就是notify()方法僅通知“一個線程” |
notifyAll() | 使所有正在等待隊列中等待同一共享資源的 “全部線程” 退出等待隊列,進入可運行狀態。此時,優先級最高的那個線程最先執行,但也有可能是隨機執行,這取決于JVM虛擬機的實現 |
wait() | 使調用該方法的線程釋放共享資源鎖,然后從運行狀態退出,進入等待隊列,直到被再次喚醒 |
wait(long) | 超時等待一段時間,這里的參數時間是毫秒,也就是等待長達n毫秒,如果沒有通知就超時返回 |
wait(long,int) | 對于超時時間更細力度的控制,可以達到納秒 |
MyList.java
public class MyList { private static Listlist = new ArrayList (); public static void add() { list.add("anyString"); } public static int size() { return list.size(); } }
ThreadA.java
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(); } } }
ThreadB.java
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(); } } }
Run.java
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(); } } }
運行結果:
從運行結果:"wait end 1521967322359"最后輸出可以看出,notify()執行后并不會立即釋放鎖。下面我們會補充介紹這個知識點。
synchronized關鍵字可以將任何一個Object對象作為同步對象來看待,而Java為每個Object都實現了等待/通知(wait/notify)機制的相關方法,它們必須用在synchronized關鍵字同步的Object的臨界區內。通過調用wait()方法可以使處于臨界區內的線程進入等待狀態,同時釋放被同步對象的鎖。而notify()方法可以喚醒一個因調用wait操作而處于阻塞狀態中的線程,使其進入就緒狀態。被重新喚醒的線程會視圖重新獲得臨界區的控制權也就是鎖,并繼續執行wait方法之后的代碼。如果發出notify操作時沒有處于阻塞狀態中的線程,那么該命令會被忽略。
如果我們這里不通過等待/通知(wait/notify)機制實現,而是使用如下的while循環實現的話,我們上面也講過會有很大的弊端。
while(MyList.size() == 5){ doSomething(); }2.2線程的基本狀態
上面幾章的學習中我們已經掌握了與線程有關的大部分API,這些API可以改變線程對象的狀態。如下圖所示:
新建(new):新創建了一個線程對象。
可運行(runnable):線程對象創建后,其他線程(比如main線程)調用了該對象的start()方法。該狀態的線程位于可運行線程池中,等待被線程調度選中,獲 取cpu的使用權。
運行(running):可運行狀態(runnable)的線程獲得了cpu時間片(timeslice),執行程序代碼。
阻塞(block):阻塞狀態是指線程因為某種原因放棄了cpu使用權,也即讓出了cpu timeslice,暫時停止運行。直到線程進入可運行(runnable)狀態,才有 機會再次獲得cpu timeslice轉到運行(running)狀態。阻塞的情況分三種:
(一). 等待阻塞:運行(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)狀態。
死亡(dead):線程run()、main()方法執行結束,或者因異常退出了run()方法,則該線程結束生命周期。死亡的線程不可再次復生。
備注:
可以用早起坐地鐵來比喻這個過程:
還沒起床:sleeping
起床收拾好了,隨時可以坐地鐵出發:Runnable
等地鐵來:Waiting
地鐵來了,但要排隊上地鐵:I/O阻塞
上了地鐵,發現暫時沒座位:synchronized阻塞
地鐵上找到座位:Running
到達目的地:Dead
2.3 notify()鎖不釋放當方法wait()被執行后,鎖自動被釋放,但執行玩notify()方法后,鎖不會自動釋放。必須執行完otify()方法所在的synchronized代碼塊后才釋放。
下面我們通過代碼驗證一下:
(完整代碼:https://github.com/Snailclimb/threadDemo/tree/master/src/wait_notifyHoldLock)
帶wait方法的synchronized代碼塊
synchronized (lock) { System.out.println("begin wait() ThreadName=" + Thread.currentThread().getName()); lock.wait(); System.out.println(" end wait() ThreadName=" + Thread.currentThread().getName()); }
帶notify方法的synchronized代碼塊
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代碼塊。
運行效果如下:
這也驗證了我們剛開始的結論:必須執行完notify()方法所在的synchronized代碼塊后才釋放。
當線程呈wait狀態時,對線程對象調用interrupt方法會出現InterrupedException異常。
Service.java
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了!"); } } }
ThreadA.java
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); } }
Test.java
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(); } } }
運行結果:
參考:
《Java多線程編程核心技術》
《Java并發編程的藝術》
如果你覺得博主的文章不錯,歡迎轉發點贊。你能從中學到知識就是我最大的幸運。
歡迎關注我的微信公眾號:“Java面試通關手冊”(分享各種Java學習資源,面試題,以及企業級Java實戰項目回復關鍵字免費領取)。另外我創建了一個Java學習交流群(群號:174594747),歡迎大家加入一起學習,這里更有面試,學習視頻等資源的分享。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69100.html
摘要:在這個等待通知機制中,我們需要考慮以下四個要素。何時等待線程要求的條件不滿足就等待。是會隨機地通知等待隊列中的一個線程,而會通知等待隊列中的所有線程。 由上一篇文章你應該已經知道,在 破壞占用且等待條件 的時候,如果轉出賬本和轉入賬本不滿足同時在文件架上這個條件,就用死循環的方式來循環等待,核心代碼如下: // 一次性申請轉出賬戶和轉入賬戶,直到成功 while(!actr.apply...
摘要:返回與此鎖相關聯的給定條件等待的線程數的估計。查詢是否有線程正在等待獲取此鎖。為公平鎖,為非公平鎖線程運行了獲得鎖定運行結果公平鎖的運行結果是有序的。 系列文章傳送門: Java多線程學習(一)Java多線程入門 Java多線程學習(二)synchronized關鍵字(1) java多線程學習(二)synchronized關鍵字(2) Java多線程學習(三)volatile關鍵字 ...
摘要:線程通信的目標是使線程間能夠互相發送信號。但是,這個標志已經被第一個喚醒的線程清除了,所以其余醒來的線程將回到等待狀態,直到下次信號到來。如果方法調用,而非,所有等待線程都會被喚醒并依次檢查信號值。 線程通信的目標是使線程間能夠互相發送信號。另一方面,線程通信使線程能夠等待其他線程的信號。 showImg(http://segmentfault.com/img/bVbPLD); 例...
摘要:執行會重新將設置為,并且通知喚醒其中一個若有的話在方法中調用了函數而處于等待狀態的線程。除此之外,我們需要記錄同一個線程重復對一個鎖對象加鎖的次數。競爭失敗的線程處于就緒狀態,長期競爭失敗的線程就會饑餓。 tutorials site Locks in java Locks (and other more advanced synchronization mechanisms...
閱讀 3154·2021-11-22 14:45
閱讀 3300·2019-08-29 13:11
閱讀 2306·2019-08-29 12:31
閱讀 922·2019-08-29 11:21
閱讀 2991·2019-08-29 11:09
閱讀 3617·2019-08-28 18:11
閱讀 1420·2019-08-26 13:58
閱讀 1273·2019-08-26 13:27