摘要:在這個等待通知機制中,我們需要考慮以下四個要素。何時等待線程要求的條件不滿足就等待。是會隨機地通知等待隊列中的一個線程,而會通知等待隊列中的所有線程。
由上一篇文章你應該已經(jīng)知道,在 破壞占用且等待條件 的時候,如果轉出賬本和轉入賬本不滿足同時在文件架上這個條件,就用死循環(huán)的方式來循環(huán)等待,核心代碼如下:
// 一次性申請轉出賬戶和轉入賬戶,直到成功 while(!actr.apply(this, target)) ;
如果 apply() 操作耗時非常短,而且并發(fā)沖突量也不大時,這個方案還挺不錯的,因為這種場景下,循環(huán)上幾次或者幾十次就能一次性獲取轉出賬戶和轉入賬戶了。但是如果 apply() 操作耗時長,或者并發(fā)沖突量大的時候,可能要循環(huán)上萬次才能獲取到鎖,太消耗 CPU 了。
其實在這種場景下,最好的方案應該是:如果線程要求的條件(轉出賬本和轉入賬本同在文件架上)不滿足,則線程阻塞自己,進入等待狀態(tài);當線程要求的條件(轉出賬本和轉入賬本同在文件架上)滿足后, 通知等待的線程重新執(zhí)行。其中,使用線程阻塞的方式就能避免循環(huán)等待消耗 CPU 的問題。
下面我們就來看看 Java 語言是如何支持 等待 - 通知機制
這里直接給出 等待 - 通知機制 的相關步驟:
線程首先獲取互斥鎖,當線程要求的條件不滿足時,釋放互斥鎖,進入等待狀態(tài);當要求的條件滿足時,通知其他等待的線程,重新獲取互斥鎖.
用 synchronized 實現(xiàn)等待 - 通知機制在 Java 語言里,等待 - 通知機制可以有多種實現(xiàn)方式,比如 Java 語言內置的 synchronized 配合 wait()、notify()、notifyAll() 這三個方法就能輕松實現(xiàn)。
先用 synchronized 實現(xiàn)互斥鎖。在下面這個圖里,左邊有一個等待隊列,同一時刻,只允許一個線程進入 synchronized 保護的臨界區(qū),當有一個線程進入臨界區(qū)后,其他線程就只能進入圖中左邊的等待隊列里等待。 這個等待隊列和互斥鎖是一對一的關系,每個互斥鎖都有自己獨立的等待隊列。
wait() 操作工作原理圖
在并發(fā)程序中,當一個線程進入臨界區(qū)后,由于某些條件不滿足,需要進入等待狀態(tài),Java 對象的 wait() 方法就能夠滿足這種需求。如上圖所示,當調用 wait() 方法后,當前線程就會被阻塞,并且進入到右邊的等待隊列中,這個等待隊列也是互斥鎖的等待隊列。 線程在進入等待隊列的同時,會釋放持有的互斥鎖,線程釋放鎖后,其他線程就有機會獲得鎖,并進入臨界區(qū)了。
那線程要求的條件滿足時,該怎么通知這個等待的線程呢?很簡單,就是 Java 對象的 notify() 和 notifyAll() 方法。我在下面這個圖里為你大致描述了這個過程,當條件滿足時調用 notify(),會通知等待隊列(互斥鎖的等待隊列)中的線程,告訴它條件曾經(jīng)滿足過
notify() 操作工作原理圖
為什么說是曾經(jīng)滿足過呢?因為 notify() 只能保證在通知時間點,條件是滿足的。而被通知線程的執(zhí)行時間點和通知的時間點基本上不會重合,所以當線程執(zhí)行的時候,很可能條件已經(jīng)不滿足了(可能會有其他線程插隊)。這一點你需要格外注意。除此之外,還有一個需要注意的點,被通知的線程要想重新執(zhí)行,仍然需要獲取到互斥鎖(因為曾經(jīng)獲取的鎖在調用 wait() 時已經(jīng)釋放了)。
注意 wait()、notify()、notifyAll() 方法操作的等待隊列是互斥鎖的等待隊列,所以方法要使用在
鎖上,synchronized 鎖定的是 this,那么對應的一定是 this.wait()、this.notify()、this.notifyAll();。而且 wait()、notify()、notifyAll() 這三個方法能夠被調用的前提是已經(jīng)獲取了相應的互斥鎖,所以我們會發(fā)現(xiàn) wait()、notify()、notifyAll() 都是在 synchronized{}內部被調用的。如果在 synchronized{}外部調用,或者鎖定的 this,而用 target.wait() 調用的話,JVM 會拋出一個運行時異常:java.lang.IllegalMonitorStateException
等待 - 通知機制的基本原理搞清楚后,我們來看看它如何解決一次性申請轉出賬戶和轉入賬戶的問題。在這個等待 - 通知機制中,我們需要考慮以下四個要素。
互斥鎖:上一篇文章我們提到 Allocator 需要是單例的,所以我們可以用 this 作為互斥鎖。
線程要求的條件:轉出賬戶和轉入賬戶都沒有被分配過。
何時等待:線程要求的條件不滿足就等待。
何時通知:當有線程釋放賬戶時就通知。
注意下面的判斷方式
while(條件不滿足) { wait(); }
利用這種范式可以解決上面提到的條件曾經(jīng)滿足過的情況。至于為什么這么寫,后面講解 管程的時候會在詳細解釋。
來看完成后的代碼
class Allocator { private List盡量使用 notifyAll()
在上面的代碼中,我用的是 notifyAll() 來實現(xiàn)通知機制,為什么不使用 notify() 呢?這二者是有區(qū)別的。
notify() 是會隨機地通知等待隊列中的一個線程,而 notifyAll() 會通知等待隊列中的所有線程。
從感覺上來講,應該是 notify() 更好一些,因為即便通知所有線程,也只有一個線程能夠進入臨界區(qū)。但實際上使用 notify() 也很有風險,它的風險在于可能導致某些線程永遠不會被通知到。
假設我們有資源 A、B、C、D,線程 1 申請到了 AB,線程 2 申請到了 CD,此時線程 3 申請 AB,會進入等待隊列(AB 分配給線程 1,線程 3 要求的條件不滿足),線程 4 申請 CD 也會進入等待隊列。我們再假設之后線程 1 歸還了資源 AB,如果使用 notify() 來通知等待隊列中的線程,有可能被通知的是線程 4,但線程 4 申請的是 CD,所以此時線程 4 還是會繼續(xù)等待,而真正該喚醒的線程 3 就再也沒有機會被喚醒了。
所以除非經(jīng)過深思熟慮,否則盡量使用 notifyAll()。總結
Java 語言的這種實現(xiàn),背后的理論模型其實是管程,后面會專門介紹管程。現(xiàn)在你只需要能夠熟練使用就可以了。
思考:wait() 方法和 sleep() 方法都能讓當前線程掛起一段時間,那它們的區(qū)別是什么?
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://m.hztianpu.com/yun/74187.html
摘要:請參看前一篇文章并發(fā)學習筆記一原子性可見性有序性問題六等待通知機制什么是等待通知機制當線程不滿足某個條件,則進入等待狀態(tài)如果線程滿足要求的某個條件后,則通知等待的線程重新執(zhí)行。經(jīng)極客時間并發(fā)編程實戰(zhàn)專欄內容學習整理 請參看前一篇文章:Java 并發(fā)學習筆記(一)——原子性、可見性、有序性問題 六、等待—通知機制 什么是等待通知—機制?當線程不滿足某個條件,則進入等待狀態(tài);如果線程滿足要...
摘要:對象改變條件對象當前線程要等待線程終止之后才能從返回。如果線程在上的操作中被中斷,通道會被關閉,線程的中斷狀態(tài)會被設置,并得到一個。清除線程的中斷狀態(tài)。非公平性鎖雖然可能造成饑餓,但極少的線程切換,保證其更大的吞吐量。 聲明:Java并發(fā)的內容是自己閱讀《Java并發(fā)編程實戰(zhàn)》和《Java并發(fā)編程的藝術》整理來的。 showImg(https://segmentfault.com/im...
摘要:是要和配合使用的也就是和是綁定在一起的,而的實現(xiàn)原理又依賴于,自然而然作為的一個內部類無可厚非。示意圖如下是的內部類,因此每個能夠訪問到提供的方法,相當于每個都擁有所屬同步器的引用。Condition簡介Object類是Java中所有類的父類, 在線程間實現(xiàn)通信的往往會應用到Object的幾個方法: wait(),wait(long timeout),wait(long timeout, i...
摘要:運行可運行狀態(tài)的線程獲得了時間片,執(zhí)行程序代碼。阻塞的情況分三種一等待阻塞運行的線程執(zhí)行方法,會把該線程放入等待隊列中。死亡線程方法執(zhí)行結束,或者因異常退出了方法,則該線程結束生命周期。死亡的線程不可再次復生。 系列文章傳送門: Java多線程學習(一)Java多線程入門 Java多線程學習(二)synchronized關鍵字(1) java多線程學習(二)synchronized關鍵...
閱讀 3440·2021-11-22 09:34
閱讀 728·2021-11-19 11:29
閱讀 1409·2019-08-30 15:43
閱讀 2291·2019-08-30 14:24
閱讀 1919·2019-08-29 17:31
閱讀 1287·2019-08-29 17:17
閱讀 2675·2019-08-29 15:38
閱讀 2850·2019-08-26 12:10