摘要:是要和配合使用的也就是和是綁定在一起的,而的實現原理又依賴于,自然而然作為的一個內部類無可厚非。示意圖如下是的內部類,因此每個能夠訪問到提供的方法,相當于每個都擁有所屬同步器的引用。
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 cancelled
unlinkCancelledWaiters(); // 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;
} //將當前線程包裝成Node
Node node = new Node(Thread.currentThread(), Node.CONDITION); if (t == null) //t==null,同步隊列為空的情況
firstWaiter = node; else
//尾插法
t.nextWaiter = node; //更新lastWaiter
lastWaiter = 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. 先檢測當前線程是否已經獲取lock
if (!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. 更新狀態為0
if (!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失敗會直接進入到同步隊列。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/7186.html
摘要:返回與此鎖相關聯的給定條件等待的線程數的估計。查詢是否有線程正在等待獲取此鎖。為公平鎖,為非公平鎖線程運行了獲得鎖定運行結果公平鎖的運行結果是有序的。 系列文章傳送門: Java多線程學習(一)Java多線程入門 Java多線程學習(二)synchronized關鍵字(1) java多線程學習(二)synchronized關鍵字(2) Java多線程學習(三)volatile關鍵字 ...
摘要:語言在之前,提供的唯一的并發原語就是管程,而且之后提供的并發包,也是以管程技術為基礎的。但是管程更容易使用,所以選擇了管程。線程進入條件變量的等待隊列后,是允許其他線程進入管程的。并發編程里兩大核心問題互斥和同步,都可以由管程來幫你解決。 并發編程這個技術領域已經發展了半個世紀了。有沒有一種核心技術可以很方便地解決我們的并發問題呢?這個問題, 我會選擇 Monitor(管程)技術。Ja...
摘要:造成當前線程在接到信號被中斷或到達指定最后期限之前一直處于等待狀態。該線程從等待方法返回前必須獲得與相關的鎖。如果線程已經獲取了鎖,則將喚醒條件隊列的首節點。 一、寫在前面 在前幾篇我們聊了 AQS、CLH、ReentrantLock、ReentrantReadWriteLock等的原理以及其源碼解讀,具體參見專欄 《非學無以廣才》 這章我們一起聊聊顯示的Condition 對象。 ...
摘要:最后一直調用函數判斷節點是否被轉移到隊列上,也就是中等待獲取鎖的隊列。這樣的話,函數中調用函數就會返回,導致函數進入最后一步重新獲取鎖的狀態。函數其實就做了一件事情,就是不斷嘗試調用函數,將隊首的一個節點轉移到隊列中,直到轉移成功。 ?我在前段時間寫了一篇關于AQS源碼解析的文章AbstractQueuedSynchronizer超詳細原理解析,在文章里邊我說JUC包中的大部分多線程相...
摘要:調用代碼的線程就持有了對象監視器,其他線程只有等待鎖被釋放時再次爭搶。使用多個對象,可以喚醒部分指定線程,有助于提升程序運行的效率。方法的作用是返回等待與此鎖定相關給定條件的線程估計數。線程在等待時間到達前,可以被其他線程提前喚醒。 調用lock.lock()代碼的線程就持有了對象監視器,其他線程只有等待鎖被釋放時再次爭搶。效果和使用synchronized關鍵字一樣,線程之間執行的...
閱讀 2106·2021-11-23 09:51
閱讀 2844·2021-11-22 15:35
閱讀 2942·2019-08-30 15:53
閱讀 1044·2019-08-30 14:04
閱讀 3283·2019-08-29 12:39
閱讀 1811·2019-08-28 17:57
閱讀 1101·2019-08-26 13:39
閱讀 559·2019-08-26 13:34