日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java并发编程,Condition的await和signal等待通知机制

發布時間:2025/3/15 java 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java并发编程,Condition的await和signal等待通知机制 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Condition簡介

Object類是Java中所有類的父類, 在線程間實現通信的往往會應用到Object的幾個方法: wait(),wait(long timeout),wait(long timeout, int nanos)與notify(),notifyAll() 實現等待/通知機制,同樣的, 在Java Lock體系下依然會有同樣的方法實現等待/通知機制。 從整體上來看Object的wait和notify/notify是與對象監視器配合完成線程間的等待/通知機制,Condition與Lock配合完成等待/通知機制, 前者是Java底層級別的,后者是語言級別的,具有更高的可控制性和擴展性。 兩者除了在使用方式上不同外,在功能特性上還是有很多的不同:

  • Condition能夠支持不響應中斷,而通過使用Object方式不支持

  • Condition能夠支持多個等待隊列(new 多個Condition對象),而Object方式只能支持一個

  • Condition能夠支持超時時間的設置,而Object不支持

參照Object的wait和notify/notifyAll方法,Condition也提供了同樣的方法:

  • 針對Object的wait方法

void await() throws InterruptedException//當前線程進入等待狀態,如果在等待狀態中被中斷會拋出被中斷異常long awaitNanos(long nanosTimeout)//當前線程進入等待狀態直到被通知,中斷或者超時boolean await(long time, TimeUnit unit)throws InterruptedException//同第二種,支持自定義時間單位boolean awaitUntil(Date deadline) throws InterruptedException//當前線程進入等待狀態直到被通知,中斷或者到了某個時間復制代碼
  • 針對Object的notify/notifyAll方法

void signal()//喚醒一個等待在condition上的線程,將該線程從等待隊列中轉移到同步隊列中,如果在同步隊列中能夠競爭到Lock則可以從等待方法中返回。void signalAll()//與1的區別在于能夠喚醒所有等待在condition上的線程復制代碼

Condition實現原理分析

等待隊列

創建一個Condition對象是通過lock.newCondition(), 而這個方法實際上是會創建ConditionObject對象,該類是AQS的一個內部類。 Condition是要和Lock配合使用的也就是Condition和Lock是綁定在一起的,而lock的實現原理又依賴于AQS, 自然而然ConditionObject作為AQS的一個內部類無可厚非。 我們知道在鎖機制的實現上,AQS內部維護了一個同步隊列,如果是獨占式鎖的話, 所有獲取鎖失敗的線程的尾插入到同步隊列, 同樣的,Condition內部也是使用同樣的方式,內部維護了一個等待隊列, 所有調用condition.await方法的線程會加入到等待隊列中,并且線程狀態轉換為等待狀態。 另外注意到ConditionObject中有兩個成員變量:

/** First node of condition queue. */private transient Node firstWaiter;/** Last node of condition queue. */private transient Node lastWaiter;復制代碼

ConditionObject通過持有等待隊列的頭尾指針來管理等待隊列。 注意Node類復用了在AQS中的Node類,Node類有這樣一個屬性:

//后繼節點Node nextWaiter;復制代碼

等待隊列是一個單向隊列,而在之前說AQS時知道同步隊列是一個雙向隊列。

等待隊列示意圖:


注意: 我們可以多次調用lock.newCondition()方法創建多個Condition對象,也就是一個lLock可以持有多個等待隊列。 利用Object的方式實際上是指在對象Object對象監視器上只能擁有一個同步隊列和一個等待隊列; 并發包中的Lock擁有一個同步隊列和多個等待隊列。示意圖如下:


ConditionObject是AQS的內部類, 因此每個ConditionObject能夠訪問到AQS提供的方法,相當于每個Condition都擁有所屬同步器的引用。

await實現原理

當調用condition.await()方法后會使得當前獲取lock的線程進入到等待隊列, 如果該線程能夠從await()方法返回的話一定是該線程獲取了與condition相關聯的lock。 await()方法源碼如下:

public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); // 1. 將當前線程包裝成Node,尾插法插入到等待隊列中Node node = addConditionWaiter(); // 2. 釋放當前線程所占用的lock,在釋放的過程中會喚醒同步隊列中的下一個節點int savedState = fullyRelease(node); int interruptMode = 0; while (!isOnSyncQueue(node)) { // 3. 當前線程進入到等待狀態LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break;} // 4. 自旋等待獲取到同步狀態(即獲取到lock)if (acquireQueued(node, savedState) && interruptMode != THROW_IE)interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelledunlinkCancelledWaiters(); // 5. 處理被中斷的情況if (interruptMode != 0)reportInterruptAfterWait(interruptMode); }復制代碼

當前線程調用condition.await()方法后,會使得當前線程釋放lock然后加入到等待隊列中, 直至被signal/signalAll后會使得當前線程從等待隊列中移至到同步隊列中去, 直到獲得了lock后才會從await方法返回,或者在等待時被中斷會做中斷處理。

addConditionWaiter()將當前線程添加到等待隊列中,其源碼如下:

private Node addConditionWaiter() { Node t = lastWaiter; // If lastWaiter is cancelled, clean out.if (t != null && t.waitStatus != Node.CONDITION) {unlinkCancelledWaiters();t = lastWaiter;} //將當前線程包裝成NodeNode node = new Node(Thread.currentThread(), Node.CONDITION); if (t == null) //t==null,同步隊列為空的情況firstWaiter = node; else //尾插法t.nextWaiter = node; //更新lastWaiterlastWaiter = node; return node; }復制代碼

這里通過尾插法將當前線程封裝的Node插入到等待隊列中, 同時可以看出等待隊列是一個不帶頭結點的鏈式隊列,之前我們學習AQS時知道同步隊列是一個帶頭結點的鏈式隊列。

將當前節點插入到等待對列之后,使用fullyRelease(0)方法釋放當前線程釋放lock,源碼如下:

final int fullyRelease(Node node) { boolean failed = true; try { int savedState = getState(); if (release(savedState)) { //成功釋放同步狀態failed = false; return savedState;} else { //不成功釋放同步狀態拋出異常throw new IllegalMonitorStateException();}} finally { if (failed)node.waitStatus = Node.CANCELLED;} }復制代碼

調用AQS的模板方法release()方法釋放AQS的同步狀態并且喚醒在同步隊列中頭結點的后繼節點引用的線程, 如果釋放成功則正常返回,若失敗的話就拋出異常。

如何從await()方法中退出?再看await()方法有這樣一段代碼:

while (!isOnSyncQueue(node)) { // 3. 當前線程進入到等待狀態LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; }復制代碼

當線程第一次調用condition.await()方法時, 會進入到這個while()循環中,然后通過LockSupport.park(this)方法使得當前線程進入等待狀態, 那么要想退出這個await方法就要先退出這個while循環,退出while循環的出口有2個:


  • break退出while循環


  • while循環中的邏輯判斷為false

  • 第1種情況的條件是當前等待的線程被中斷后會走到break退出,

    第2種情況是當前節點被移動到了同步隊列中,(即另外線程調用的condition的signal或者signalAll方法), while中邏輯判斷為false后結束while循環。

    當退出while循環后就會調用acquireQueued(node, savedState),該方法的作用是 在自旋過程中線程不斷嘗試獲取同步狀態,直至成功(線程獲取到lock)。

    這樣就說明了退出await方法必須是已經獲得了Condition引用(關聯)的Lock。

    await方法示意圖如下:


    調用condition.await方法的線程必須是已經獲得了lock,也就是當前線程是同步隊列中的頭結點。 調用該方法后會使得當前線程所封裝的Node尾插入到等待隊列中。

    超時機制的支持

    condition還額外支持了超時機制,使用者可調用方法awaitNanos,awaitUtil。 這兩個方法的實現原理,基本上與AQS中的tryAcquire方法如出一轍。

    不響應中斷的支持

    調用condition.awaitUninterruptibly()方法,該方法的源碼為:

    public final void awaitUninterruptibly() { Node node = addConditionWaiter(); int savedState = fullyRelease(node); boolean interrupted = false; while (!isOnSyncQueue(node)) { LockSupport.park(this); if (Thread.interrupted())interrupted = true;} if (acquireQueued(node, savedState) || interrupted)selfInterrupt(); }復制代碼

    與上面的await方法基本一致,只不過減少了對中斷的處理, 并省略了reportInterruptAfterWait方法拋被中斷的異常。

    signal和signalAll實現原理

    調用Condition的signal或者signalAll方法可以將 等待隊列中等待時間最長的節點移動到同步隊列中,使得該節點能夠有機會獲得lock。 按照等待隊列是先進先出(FIFO)的, 所以等待隊列的頭節點必然會是等待時間最長的節點, 也就是每次調用condition的signal方法是將頭節點移動到同步隊列中。 signal()源碼如下:

    public final void signal() { //1. 先檢測當前線程是否已經獲取lockif (!isHeldExclusively()) throw new IllegalMonitorStateException(); //2. 獲取等待隊列中第一個節點,之后的操作都是針對這個節點 Node first = firstWaiter; if (first != null)doSignal(first); }復制代碼

    signal方法首先會檢測當前線程是否已經獲取lock, 如果沒有獲取lock會直接拋出異常,如果獲取的話再得到等待隊列的頭指針引用的節點,doSignal方法也是基于該節點。 doSignal方法源碼如下:

    private void doSignal(Node first) { do { if ( (firstWaiter = first.nextWaiter) == null)lastWaiter = null; //1. 將頭結點從等待隊列中移除first.nextWaiter = null; //2. while中transferForSignal方法對頭結點做真正的處理} while (!transferForSignal(first) &&(first = firstWaiter) != null); }復制代碼

    真正對頭節點做處理的是transferForSignal(),該方法源碼如下:

    final boolean transferForSignal(Node node) { //1. 更新狀態為0if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; //2.將該節點移入到同步隊列中去Node p = enq(node); int ws = p.waitStatus; if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) LockSupport.unpark(node.thread); return true; }復制代碼

    這段代碼主要做了兩件事情:

    • 1.將頭結點的狀態更改為CONDITION

    • 2.調用enq方法,將該節點尾插入到同步隊列中

    調用condition的signal的前提條件是 當前線程已經獲取了lock,該方法會使得等待隊列中的頭節點(等待時間最長的那個節點)移入到同步隊列, 而移入到同步隊列后才有機會使得等待線程被喚醒, 即從await方法中的LockSupport.park(this)方法中返回,從而才有機會使得調用await方法的線程成功退出。

    signal方法示意圖如下:


    signalAll

    sigllAll與sigal方法的區別體現在doSignalAll方法上。doSignalAll()的源碼如下:

    private void doSignalAll(Node first) {lastWaiter = firstWaiter = null; do { Node next = first.nextWaiter;first.nextWaiter = null;transferForSignal(first);first = next;} while (first != null); }復制代碼

    doSignal方法只會對等待隊列的頭節點進行操作,而doSignalAll方法將等待隊列中的每一個節點都移入到同步隊列中, 即“通知”當前調用condition.await()方法的每一個線程。

    await與signal和signalAll的結合

    await和signal和signalAll方法就像一個開關控制著線程A(等待方)和線程B(通知方)。 它們之間的關系可以用下面一個圖來表現得更加貼切:


    線程awaitThread先通過lock.lock()方法獲取鎖成功后調用了condition.await方法進入等待隊列, 而另一個線程signalThread通過lock.lock()方法獲取鎖成功后調用了condition.signal或者signalAll方法, 使得線程awaitThread能夠有機會移入到同步隊列中, 當其他線程釋放lock后使得線程awaitThread能夠有機會獲取lock, 從而使得線程awaitThread能夠從await方法中退出,然后執行后續操作。 如果awaitThread獲取lock失敗會直接進入到同步隊列。



    轉載于:https://juejin.im/post/5ce63cc4f265da1bb564d0f8

    總結

    以上是生活随笔為你收集整理的Java并发编程,Condition的await和signal等待通知机制的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。