成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

[Java并發(fā)-5]用“等待-通知”機(jī)制優(yōu)化循環(huán)等待

fxp / 1457人閱讀

摘要:在這個等待通知機(jī)制中,我們需要考慮以下四個要素。何時等待線程要求的條件不滿足就等待。是會隨機(jī)地通知等待隊(duì)列中的一個線程,而會通知等待隊(duì)列中的所有線程。

由上一篇文章你應(yīng)該已經(jīng)知道,在 破壞占用且等待條件 的時候,如果轉(zhuǎn)出賬本和轉(zhuǎn)入賬本不滿足同時在文件架上這個條件,就用死循環(huán)的方式來循環(huán)等待,核心代碼如下:

// 一次性申請轉(zhuǎn)出賬戶和轉(zhuǎn)入賬戶,直到成功
while(!actr.apply(this, target))
  ;

如果 apply() 操作耗時非常短,而且并發(fā)沖突量也不大時,這個方案還挺不錯的,因?yàn)檫@種場景下,循環(huán)上幾次或者幾十次就能一次性獲取轉(zhuǎn)出賬戶和轉(zhuǎn)入賬戶了。但是如果 apply() 操作耗時長,或者并發(fā)沖突量大的時候,可能要循環(huán)上萬次才能獲取到鎖,太消耗 CPU 了。

其實(shí)在這種場景下,最好的方案應(yīng)該是:如果線程要求的條件(轉(zhuǎn)出賬本和轉(zhuǎn)入賬本同在文件架上)不滿足,則線程阻塞自己,進(jìn)入等待狀態(tài);當(dāng)線程要求的條件(轉(zhuǎn)出賬本和轉(zhuǎn)入賬本同在文件架上)滿足后, 通知等待的線程重新執(zhí)行。其中,使用線程阻塞的方式就能避免循環(huán)等待消耗 CPU 的問題。

下面我們就來看看 Java 語言是如何支持 等待 - 通知機(jī)制

這里直接給出 等待 - 通知機(jī)制 的相關(guān)步驟:

線程首先獲取互斥鎖,當(dāng)線程要求的條件不滿足時,釋放互斥鎖,進(jìn)入等待狀態(tài);當(dāng)要求的條件滿足時,通知其他等待的線程,重新獲取互斥鎖.

用 synchronized 實(shí)現(xiàn)等待 - 通知機(jī)制

在 Java 語言里,等待 - 通知機(jī)制可以有多種實(shí)現(xiàn)方式,比如 Java 語言內(nèi)置的 synchronized 配合 wait()、notify()、notifyAll() 這三個方法就能輕松實(shí)現(xiàn)。

先用 synchronized 實(shí)現(xiàn)互斥鎖。在下面這個圖里,左邊有一個等待隊(duì)列,同一時刻,只允許一個線程進(jìn)入 synchronized 保護(hù)的臨界區(qū),當(dāng)有一個線程進(jìn)入臨界區(qū)后,其他線程就只能進(jìn)入圖中左邊的等待隊(duì)列里等待。 這個等待隊(duì)列和互斥鎖是一對一的關(guān)系,每個互斥鎖都有自己獨(dú)立的等待隊(duì)列。


wait() 操作工作原理圖

在并發(fā)程序中,當(dāng)一個線程進(jìn)入臨界區(qū)后,由于某些條件不滿足,需要進(jìn)入等待狀態(tài),Java 對象的 wait() 方法就能夠滿足這種需求。如上圖所示,當(dāng)調(diào)用 wait() 方法后,當(dāng)前線程就會被阻塞,并且進(jìn)入到右邊的等待隊(duì)列中,這個等待隊(duì)列也是互斥鎖的等待隊(duì)列。 線程在進(jìn)入等待隊(duì)列的同時,會釋放持有的互斥鎖,線程釋放鎖后,其他線程就有機(jī)會獲得鎖,并進(jìn)入臨界區(qū)了。

那線程要求的條件滿足時,該怎么通知這個等待的線程呢?很簡單,就是 Java 對象的 notify() 和 notifyAll() 方法。我在下面這個圖里為你大致描述了這個過程,當(dāng)條件滿足時調(diào)用 notify(),會通知等待隊(duì)列(互斥鎖的等待隊(duì)列)中的線程,告訴它條件曾經(jīng)滿足過


notify() 操作工作原理圖

為什么說是曾經(jīng)滿足過呢?因?yàn)?notify() 只能保證在通知時間點(diǎn),條件是滿足的。而被通知線程的執(zhí)行時間點(diǎn)和通知的時間點(diǎn)基本上不會重合,所以當(dāng)線程執(zhí)行的時候,很可能條件已經(jīng)不滿足了(可能會有其他線程插隊(duì))。這一點(diǎn)你需要格外注意。除此之外,還有一個需要注意的點(diǎn),被通知的線程要想重新執(zhí)行,仍然需要獲取到互斥鎖(因?yàn)樵?jīng)獲取的鎖在調(diào)用 wait() 時已經(jīng)釋放了)。

注意 wait()、notify()、notifyAll() 方法操作的等待隊(duì)列是互斥鎖的等待隊(duì)列,所以方法要使用在
上,synchronized 鎖定的是 this,那么對應(yīng)的一定是 this.wait()、this.notify()、this.notifyAll();。而且 wait()、notify()、notifyAll() 這三個方法能夠被調(diào)用的前提是已經(jīng)獲取了相應(yīng)的互斥鎖,所以我們會發(fā)現(xiàn) wait()、notify()、notifyAll() 都是在 synchronized{}內(nèi)部被調(diào)用的。如果在 synchronized{}外部調(diào)用,或者鎖定的 this,而用 target.wait() 調(diào)用的話,JVM 會拋出一個運(yùn)行時異常:java.lang.IllegalMonitorStateException

一個更好地資源分配器

等待 - 通知機(jī)制的基本原理搞清楚后,我們來看看它如何解決一次性申請轉(zhuǎn)出賬戶和轉(zhuǎn)入賬戶的問題。在這個等待 - 通知機(jī)制中,我們需要考慮以下四個要素。

互斥鎖:上一篇文章我們提到 Allocator 需要是單例的,所以我們可以用 this 作為互斥鎖。

線程要求的條件:轉(zhuǎn)出賬戶和轉(zhuǎn)入賬戶都沒有被分配過。

何時等待:線程要求的條件不滿足就等待。

何時通知:當(dāng)有線程釋放賬戶時就通知。

注意下面的判斷方式

  while(條件不滿足) {
    wait();
  }

利用這種范式可以解決上面提到的條件曾經(jīng)滿足過的情況。至于為什么這么寫,后面講解 管程的時候會在詳細(xì)解釋。

來看完成后的代碼

class Allocator {
  private List als;
  // 一次性申請所有資源
  synchronized void apply(
    Object from, Object to){
    // 經(jīng)典寫法
    while(als.contains(from) ||
         als.contains(to)){
      try{
        wait();
      }catch(Exception e){
      }   
    } 
    als.add(from);
    als.add(to);  
  }
  // 歸還資源
  synchronized void free(
    Object from, Object to){
    als.remove(from);
    als.remove(to);
    notifyAll();
  }
}

盡量使用 notifyAll()

在上面的代碼中,我用的是 notifyAll() 來實(shí)現(xiàn)通知機(jī)制,為什么不使用 notify() 呢?這二者是有區(qū)別的。

notify() 是會隨機(jī)地通知等待隊(duì)列中的一個線程,而 notifyAll() 會通知等待隊(duì)列中的所有線程。

從感覺上來講,應(yīng)該是 notify() 更好一些,因?yàn)榧幢阃ㄖ芯€程,也只有一個線程能夠進(jìn)入臨界區(qū)。但實(shí)際上使用 notify() 也很有風(fēng)險(xiǎn),它的風(fēng)險(xiǎn)在于可能導(dǎo)致某些線程永遠(yuǎn)不會被通知到。

假設(shè)我們有資源 A、B、C、D,線程 1 申請到了 AB,線程 2 申請到了 CD,此時線程 3 申請 AB,會進(jìn)入等待隊(duì)列(AB 分配給線程 1,線程 3 要求的條件不滿足),線程 4 申請 CD 也會進(jìn)入等待隊(duì)列。我們再假設(shè)之后線程 1 歸還了資源 AB,如果使用 notify() 來通知等待隊(duì)列中的線程,有可能被通知的是線程 4,但線程 4 申請的是 CD,所以此時線程 4 還是會繼續(xù)等待,而真正該喚醒的線程 3 就再也沒有機(jī)會被喚醒了。

所以除非經(jīng)過深思熟慮,否則盡量使用 notifyAll()。
總結(jié)

Java 語言的這種實(shí)現(xiàn),背后的理論模型其實(shí)是管程,后面會專門介紹管程?,F(xiàn)在你只需要能夠熟練使用就可以了。

思考:wait() 方法和 sleep() 方法都能讓當(dāng)前線程掛起一段時間,那它們的區(qū)別是什么?

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/74187.html

相關(guān)文章

  • Java 并發(fā)學(xué)習(xí)筆記(二)

    摘要:請參看前一篇文章并發(fā)學(xué)習(xí)筆記一原子性可見性有序性問題六等待通知機(jī)制什么是等待通知機(jī)制當(dāng)線程不滿足某個條件,則進(jìn)入等待狀態(tài)如果線程滿足要求的某個條件后,則通知等待的線程重新執(zhí)行。經(jīng)極客時間并發(fā)編程實(shí)戰(zhàn)專欄內(nèi)容學(xué)習(xí)整理 請參看前一篇文章:Java 并發(fā)學(xué)習(xí)筆記(一)——原子性、可見性、有序性問題 六、等待—通知機(jī)制 什么是等待通知—機(jī)制?當(dāng)線程不滿足某個條件,則進(jìn)入等待狀態(tài);如果線程滿足要...

    zgbgx 評論0 收藏0
  • Java并發(fā)

    摘要:對象改變條件對象當(dāng)前線程要等待線程終止之后才能從返回。如果線程在上的操作中被中斷,通道會被關(guān)閉,線程的中斷狀態(tài)會被設(shè)置,并得到一個。清除線程的中斷狀態(tài)。非公平性鎖雖然可能造成饑餓,但極少的線程切換,保證其更大的吞吐量。 聲明:Java并發(fā)的內(nèi)容是自己閱讀《Java并發(fā)編程實(shí)戰(zhàn)》和《Java并發(fā)編程的藝術(shù)》整理來的。 showImg(https://segmentfault.com/im...

    SKYZACK 評論0 收藏0
  • Java并發(fā)編程,Condition的await和signal等待通知機(jī)制

    摘要:是要和配合使用的也就是和是綁定在一起的,而的實(shí)現(xiàn)原理又依賴于,自然而然作為的一個內(nèi)部類無可厚非。示意圖如下是的內(nèi)部類,因此每個能夠訪問到提供的方法,相當(dāng)于每個都擁有所屬同步器的引用。Condition簡介Object類是Java中所有類的父類, 在線程間實(shí)現(xiàn)通信的往往會應(yīng)用到Object的幾個方法: wait(),wait(long timeout),wait(long timeout, i...

    el09xccxy 評論0 收藏0
  • Java線程匯總

    摘要:線程需要避免竟態(tài),死鎖以及很多其他共享狀態(tài)的并發(fā)性問題。用戶線程在前臺,守護(hù)線程在后臺運(yùn)行,為其他前臺線程提供服務(wù)。當(dāng)所有前臺線程都退出時,守護(hù)線程就會退出。線程阻塞等待獲取某個對象鎖的訪問權(quán)限。 1、多線程介紹 多線程優(yōu)點(diǎn) 資源利用率好 程序設(shè)計(jì)簡單 服務(wù)器響應(yīng)更快 多線程缺點(diǎn) 設(shè)計(jì)更復(fù)雜 上下文切換的開銷 增加資源消耗線程需要內(nèi)存維護(hù)本地的堆棧,同時需要操作系統(tǒng)資源管理線程。...

    Lsnsh 評論0 收藏0
  • Java多線程學(xué)習(xí)(四)等待/通知(wait/notify)機(jī)制

    摘要:運(yùn)行可運(yùn)行狀態(tài)的線程獲得了時間片,執(zhí)行程序代碼。阻塞的情況分三種一等待阻塞運(yùn)行的線程執(zhí)行方法,會把該線程放入等待隊(duì)列中。死亡線程方法執(zhí)行結(jié)束,或者因異常退出了方法,則該線程結(jié)束生命周期。死亡的線程不可再次復(fù)生。 系列文章傳送門: Java多線程學(xué)習(xí)(一)Java多線程入門 Java多線程學(xué)習(xí)(二)synchronized關(guān)鍵字(1) java多線程學(xué)習(xí)(二)synchronized關(guān)鍵...

    PiscesYE 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<