最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java并發(fā) 線程間的等待與通知

 更新時(shí)間:2019年10月17日 14:57:20   作者:方塊人  
這篇文章主要介紹了Java并發(fā) 線程間的等待與通知,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下

前言:

前面講完了一些并發(fā)編程的原理,現(xiàn)在我們要來學(xué)習(xí)的是線程之間的協(xié)作。通俗來說就是,當(dāng)前線程在某個(gè)條件下需要等待,不需要使用太多系統(tǒng)資源。在某個(gè)條件下我們需要去喚醒它,分配給它一定的系統(tǒng)資源,讓它繼續(xù)工作。這樣能更好的節(jié)約資源。

一、Object的wait()與notify()

基本概念:

一個(gè)線程因執(zhí)行目標(biāo)動(dòng)作的條件未能滿足而被要求暫停就是wait,而一個(gè)線程滿足執(zhí)行目標(biāo)動(dòng)作的條件之后喚醒被暫停的線程就是notify。

基本模板:

synchronized (obj){
      //保護(hù)條件不成立
      while(flag){
        //暫停當(dāng)前線程
        obj.wait();
      }
      //當(dāng)保護(hù)條件成立,即跳出while循環(huán)執(zhí)行目標(biāo)動(dòng)作
      doAction();
    }

解析wait():Object.wait()的作用是使執(zhí)行線程被暫停,該執(zhí)行線程生命周期就變更為WAITING,這里注意一下,是無限等待,直到有notify()方法通知該線程喚醒。Object.wait(long timeout)的作用是使執(zhí)行線程超過一定時(shí)間沒有被喚醒就自動(dòng)喚醒,也就是超時(shí)等待。Object.wait(long timeout,int naous)是更加精準(zhǔn)的控制時(shí)間的方法,可以控制到毫微秒。這里需要注意的是wait()會(huì)在當(dāng)前線程擁有鎖的時(shí)候才能執(zhí)行該方法并且釋放當(dāng)前線程擁有的鎖,從而讓該線程進(jìn)入等待狀態(tài),其他線程來嘗試獲取當(dāng)前鎖。也就是需要申請(qǐng)鎖與釋放鎖。

解析notify():Object.notify()方法是喚醒調(diào)用了wait()的線程,只喚醒最多一個(gè)。如果有多個(gè)線程,不一定能喚醒我們所想要的線程。Object.notifyAll()喚醒所有等待的線程。notify方法一定是通知線程先獲取到了鎖才能進(jìn)行通知。通知之后當(dāng)前的通知線程需要釋放鎖,然后由等待線程來獲取。所以涉及到了一個(gè)申請(qǐng)鎖與釋放鎖的步驟。

wait()與notify()之間存在的三大問題:

從上面的解析可以看出,notify()是無指向性的喚醒,notifyAll()是無偏差喚醒。所以會(huì)產(chǎn)生下面三個(gè)問題

過早喚醒:假設(shè)當(dāng)前有三組等待(w1,w2,w3)與通知(n1,n2,n3)線程同步在對(duì)象obj上,w1,w2的判斷喚醒條件相同,由線程n1更新條件并喚醒,w3的判斷喚醒條件不同,由n2,n3更新條件并喚醒,這時(shí)如果n1執(zhí)行了喚醒,那么不能執(zhí)行notify,因?yàn)樾枰行褍蓷l線程,只能用notifyAll(),可是用了之后w3的條件未能滿足就被叫醒,就需要一直占用資源的去等待執(zhí)行。

信號(hào)丟失:這個(gè)問題主要是程序員編程出現(xiàn)了問題,并不是內(nèi)部實(shí)現(xiàn)機(jī)制出現(xiàn)的問題。編程時(shí)如果在該使用notifyAll()的地方使用notify()那么只能喚醒一個(gè)線程,從而使其他應(yīng)該喚醒的線程未能喚醒,這就是信號(hào)丟失。如果等待線程在執(zhí)行wait()方法前沒有先判斷保護(hù)條件是否成立,就會(huì)出現(xiàn)通知線程在該等待線程進(jìn)入臨界區(qū)之前就已經(jīng)更新了相關(guān)共享變量,并且執(zhí)行了notify()方法,但是由于wait()還未能執(zhí)行,且沒有設(shè)置共享變量的判斷,所以會(huì)執(zhí)行wait()方法,導(dǎo)致線程一直處于等待狀態(tài),丟失了一個(gè)信號(hào)。

欺騙性喚醒:等待線程并不是一定有notify()/notifyAll()才能被喚醒,雖然出現(xiàn)的概率特別低,但是操作系統(tǒng)是允許這種情況發(fā)生的。

上下文切換問題:首先wait()至少會(huì)導(dǎo)致線程對(duì)相應(yīng)對(duì)象內(nèi)部鎖的申請(qǐng)與釋放。notify()/notifyAll()時(shí)需要持有相應(yīng)的對(duì)象內(nèi)部鎖并且也會(huì)釋放該鎖,會(huì)出現(xiàn)上下文切換問題其實(shí)就是從RUNNABLE狀態(tài)變?yōu)榉荝UNNABLE狀態(tài)會(huì)出現(xiàn)。

針對(duì)問題的解決方案:

信號(hào)丟失與欺騙性喚醒問題:都可以使用while循環(huán)來避免,也就是上面的模板中寫的那樣。

上下文切換問題:在保證程序正確性的情況下使用notify()代替notifyAll(),notify不會(huì)導(dǎo)致過早喚醒,所以減少了上下文的切換。并且使用了notify之后應(yīng)該盡快釋放相應(yīng)內(nèi)部鎖,從而讓wait()能夠更快的申請(qǐng)到鎖。

過早喚醒:使用java.util.concurrent.locks.Condition中的await與signal。

PS:由于Object中的wait與notify使用的是native方法,即C++編寫,這里不做源碼解析。

二、Condition中的await()與signal()

這個(gè)方法相應(yīng)的改變了上面所說的無指向性的問題,每個(gè)Condition內(nèi)部都會(huì)維護(hù)一個(gè)隊(duì)列,從而讓我們對(duì)線程之間的操作更加靈活。下面通過分析源碼讓我們了解一下內(nèi)部機(jī)制。Condition是個(gè)接口,真正的實(shí)現(xiàn)是AbstractQueuedSynchronizer中的內(nèi)部類ConditionObject。

基本屬性:

public class ConditionObject implements Condition, java.io.Serializable {
    private static final long serialVersionUID = 1173984872572414699L;
    /** First node of condition queue. */
    private transient Node firstWaiter;
    /** Last node of condition queue. */
    private transient Node lastWaiter;
}

從基本屬性中可看出維護(hù)的是雙端隊(duì)列。

await()方法解析:

public class ConditionObject implements Condition, java.io.Serializable {
  public final void await() throws InterruptedException {
   // 1. 判斷線程是否中斷
  if(Thread.interrupted()){            
    throw new InterruptedException();
  }
   // 2. 將線程封裝成一個(gè) Node 放到 Condition Queue 里面
  Node node = addConditionWaiter();
   // 3. 釋放當(dāng)前線程所獲取的所有的鎖 (PS: 調(diào)用 await 方法時(shí), 當(dāng)前線程是必須已經(jīng)獲取了獨(dú)占的鎖)       
  int savedState = fullyRelease(node);      
  int interruptMode = 0;
   // 4. 判斷當(dāng)前線程是否在 Sync Queue 里面(這里 Node 從 Condtion Queue 里面轉(zhuǎn)移到 Sync Queue 里面有兩種可能    //(1) 其他線程調(diào)用 signal 進(jìn)行轉(zhuǎn)移 (2) 當(dāng)前線程被中斷而進(jìn)行Node的轉(zhuǎn)移(就在checkInterruptWhileWaiting里面進(jìn)行轉(zhuǎn)移))
  while(!isOnSyncQueue(node)){
     // 5. 當(dāng)前線程沒在 Sync Queue 里面, 則進(jìn)行 block         
    LockSupport.park(this);
     // 6. 判斷此次線程的喚醒是否因?yàn)榫€程被中斷, 若是被中斷, 則會(huì)在checkInterruptWhileWaiting的transferAfterCancelledWait 進(jìn)行節(jié)點(diǎn)的轉(zhuǎn)移;     if((interruptMode = checkInterruptWhileWaiting(node)) != 0){  
     // 說明此是通過線程中斷的方式進(jìn)行喚醒, 并且已經(jīng)進(jìn)行了 node 的轉(zhuǎn)移, 轉(zhuǎn)移到 Sync Queue 里面
      break;                           
    }
  }
   // 7. 調(diào)用 acquireQueued在 Sync Queue 里面進(jìn)行獨(dú)占鎖的獲取, 返回值表明在獲取的過程中有沒有被中斷過
  if(acquireQueued(node, savedState) && interruptMode != THROW_IE){ 
    interruptMode = REINTERRUPT;
  }
   // 8. 通過 "node.nextWaiter != null" 判斷 線程的喚醒是中斷還是 signal?! ?//因?yàn)橥ㄟ^中斷喚醒的話, 此刻代表線程的 Node 在 Condition Queue 與 Sync Queue 里面都會(huì)存在
  if(node.nextWaiter != null){ 
     // 9. 進(jìn)行 cancelled 節(jié)點(diǎn)的清除
    unlinkCancelledWaiters();                 
  }
   // 10. "interruptMode != 0" 代表通過中斷的方式喚醒線程
  if(interruptMode != 0){  
     // 11. 根據(jù) interruptMode 的類型決定是拋出異常, 還是自己再中斷一下                 
    reportInterruptAfterWait(interruptMode);        
  }
  }
}

上面源代碼可看出Condition內(nèi)部維護(hù)的隊(duì)列是一個(gè)等待隊(duì)列,當(dāng)需要調(diào)用signal()方法時(shí)就會(huì)讓當(dāng)前線程節(jié)點(diǎn)從Condition queue轉(zhuǎn)到Sync queue隊(duì)列中去競(jìng)爭(zhēng)鎖從而喚醒。

signal()源碼解析:

public class ConditionObject implements Condition, java.io.Serializable {
  public final void signal() {
      if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
      Node first = firstWaiter;
      if (first != null)
        doSignal(first);
    }
  private void doSignal(Node first) {
      do {
        //傳入的鏈表下一個(gè)節(jié)點(diǎn)為空,則尾節(jié)點(diǎn)置空
        if ( (firstWaiter = first.nextWaiter) == null)
          lastWaiter = null;
        //當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)為空
        first.nextWaiter = null;
        //如果成功將node從condition queue轉(zhuǎn)換到sync queue,則退出循環(huán),節(jié)點(diǎn)為空了也退出循環(huán)。否則就接著在隊(duì)列中找尋節(jié)點(diǎn)進(jìn)行喚醒
      } while (!transferForSignal(first) &&
           (first = firstWaiter) != null);
    }
}

signal()會(huì)使等待隊(duì)列中的一個(gè)任意線程被喚醒,signalAll()則是喚醒該隊(duì)列中的所有線程。這樣通過不同隊(duì)列維護(hù)不同線程,就可以達(dá)到指向性的功能??梢韵蛇^早喚醒帶來的資源損耗。注意的是在使用signal()方法前需要獲取鎖,即lock(),而后需要盡快unlock(),這樣可以避免上下文切換的損耗。

總結(jié):

面向?qū)ο蟮氖澜缰?,一個(gè)類往往需要借助其他的類來一起完成計(jì)算,同樣線程的世界也是,多個(gè)線程可以同時(shí)完成一個(gè)任務(wù),通過喚醒與等待,能更好的操作線程,從而讓線程在需要使用資源的時(shí)候分配資源給它,而不使用資源的時(shí)候就可以將資源讓給其他線程操作。關(guān)于Condition中提到的Sync queue可參考Java并發(fā) 結(jié)合源碼分析AQS原理來看內(nèi)部維護(hù)的隊(duì)列是如何獲取鎖的。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • IDEA中的Structure模塊使用圖文詳解

    IDEA中的Structure模塊使用圖文詳解

    IDEA 的 Structure 功能,也是在看代碼中一個(gè)十分常用的功能,最通用的方法,就是點(diǎn)擊 IDEA 左下角的 Structure 標(biāo)簽頁即可,本文重點(diǎn)介紹IDEA中的Structure模塊使用詳解,感興趣的朋友跟隨小編一起看看吧
    2024-03-03
  • Java 數(shù)組復(fù)制clone方法實(shí)現(xiàn)詳解

    Java 數(shù)組復(fù)制clone方法實(shí)現(xiàn)詳解

    這篇文章主要介紹了Java 數(shù)組復(fù)制clone方法實(shí)現(xiàn)詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-11-11
  • Spring?AOP?的實(shí)現(xiàn)和切點(diǎn)表達(dá)式的實(shí)現(xiàn)方式

    Spring?AOP?的實(shí)現(xiàn)和切點(diǎn)表達(dá)式的實(shí)現(xiàn)方式

    本文給大家介紹了Spring?AOP的基本概念、通知類型、切點(diǎn)表達(dá)式和切面優(yōu)先級(jí),并通過示例代碼展示了如何實(shí)現(xiàn)這些功能,感興趣的朋友跟隨小編一起看看吧
    2024-12-12
  • FreeMarker如何調(diào)用Java靜態(tài)方法及靜態(tài)變量方法

    FreeMarker如何調(diào)用Java靜態(tài)方法及靜態(tài)變量方法

    這篇文章主要介紹了FreeMarker如何調(diào)用Java靜態(tài)方法及靜態(tài)變量方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • 詳解SpringBoot注解讀取配置文件的方式

    詳解SpringBoot注解讀取配置文件的方式

    這篇文章主要介紹了詳解SpringBoot注解讀取配置文件的方式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-02-02
  • java實(shí)現(xiàn)微信小程序加密數(shù)據(jù)解密算法

    java實(shí)現(xiàn)微信小程序加密數(shù)據(jù)解密算法

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)微信小程序加密數(shù)據(jù)解密算法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-09-09
  • Java線程代碼的實(shí)現(xiàn)方法

    Java線程代碼的實(shí)現(xiàn)方法

    下面小編就為大家?guī)硪黄狫ava線程代碼的實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-08-08
  • spring framework源碼調(diào)試技巧

    spring framework源碼調(diào)試技巧

    這篇文章給大家介紹了spring-framework源碼調(diào)試方法,可以直接將最新代碼clone到本地,如果想在代碼做一些注釋,也可以Fork到自己的倉(cāng)庫(kù)。本文采用Fork的方式,并添加了測(cè)試module,感興趣的朋友一起看看吧
    2021-10-10
  • 淺談Spring Security 對(duì)于靜態(tài)資源的攔截與放行

    淺談Spring Security 對(duì)于靜態(tài)資源的攔截與放行

    這篇文章主要介紹了淺談Spring Security 對(duì)于靜態(tài)資源的攔截與放行,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • Mybatis-Plus實(shí)現(xiàn)自動(dòng)生成代碼的操作步驟

    Mybatis-Plus實(shí)現(xiàn)自動(dòng)生成代碼的操作步驟

    AutoGenerator 是 MyBatis-Plus 的代碼生成器,通過 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各個(gè)模塊的代碼,極大的提升了開發(fā)效率,本文將給大家介紹Mybatis-Plus實(shí)現(xiàn)自動(dòng)生成代碼的操作步驟
    2023-10-10

最新評(píng)論

南召县| 兴业县| 宜良县| 竹溪县| 许昌市| 大方县| 贵德县| 静海县| 隆德县| 环江| 闽侯县| 龙里县| 四会市| 崇左市| 古蔺县| 宿松县| 呼玛县| 长武县| 中江县| 乌审旗| 威远县| 涞源县| 达州市| 遵化市| 察隅县| 中山市| 五常市| 青川县| 长海县| 鹤山市| 桐城市| 灵川县| 九龙坡区| 吴川市| 门源| 蒙阴县| 静海县| 大连市| 云和县| 新泰市| 西峡县|