摘要:condition用于顯式的等待通知,等待程序可以掛起并釋放鎖,喚醒后重新拿到鎖,
本文分享自華為云社區《AQS中的condition原始碼原理詳細分析》,作者:breakDawn,
condition的用法
condition用于顯式的等待通知,等待程序可以掛起并釋放鎖,喚醒后重新拿到鎖,
和直接用lock\unlock去做等待通知的區別在于,lock是不會釋放鎖的,但是利用的condition的await則可以,且喚醒后會自動重新拿回鎖,
Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); public void conditionWait() throws InterruptedException { lock.lock(); try { // if(xxxx)判斷不滿足條件,等待,釋放鎖 condition.await(); } finally { lock.unlock(); } } public void conditionSignal() throws InterruptedException { lock.lock(); try { // 做完事情了,通知condition上等待的開始搶占 condition.signal(); } finally { lock.unlock(); } }
也提供了一些支持中斷、支持超時的等待方法
condition 和 object.wait/notify的區別
- object的wait依賴sync, 只能最多有一個等待佇列, 而通過newCondition可以制造多個等待佇列
- wait不支持中斷,而condition支持
- condition支持等待特定時間
condition原理分析
超大原理流程圖
- await(), 簡單來講就是把當前執行緒放入condition的等待佇列中,然后呼叫LockSupport.park拉起執行緒,如果被其他執行緒通過signal喚醒,則放入同步佇列中競爭鎖,競爭成功則回傳,否則繼續競爭,
- signal方法,就是拿到condition的等待佇列頭節點,用cas修改節點狀態,改成功則喚醒執行緒,但有可能被別人搶先,所以需要cas操作,
代碼結構部分:
? Lock提供了newCondition介面給外部鎖呼叫
? 而newCondition()回傳的Condition是一個介面
? 這個介面的實作類是ConditionObject,放在AQS抽象類的內部類中
原理實作部分
等待佇列
- 每個condition都有一個屬于自己的等待佇列
- 每次呼叫condition.await, 就插入到等待佇列尾部
- 等待佇列插入封裝執行緒的節點時不需要在尾部CAS, 因為必須先獲取鎖,才能呼叫await,因此不用CAS競爭
- 每個Lock只有一個同步佇列(用于lock()時阻塞和競爭用), 但是可能會有多個等待佇列(用于condition的await)
等待程序
- 添加執行緒到condition的等待佇列尾部
- 釋放占用的鎖,并喚醒同步佇列的后繼節點
- 此時肯定不在aqs的同步佇列中了, 用park方法進入阻塞狀態
- 被喚醒,喚醒時可能是通過sign()被人放入了同步佇列, 也可能是被中斷喚醒,因此要做checkInterruptWhileWaiting檢查看是否繼續, 如果同意繼續,就繼續睡眠,直到進入同步佇列
- 嘗試acquireQueued競爭和搶占state同步狀態
- 退出前,順帶用unlinkCancelledWaiters清理已經不是CONDITION狀態的等待佇列節點
public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); // 添加本執行緒到等待佇列尾部 Node node = addConditionWaiter(); // 釋放鎖,喚醒同步佇列中的后繼節點 int savedState = fullyRelease(node); int interruptMode = 0; // 如果已經在同步佇列中了,說明被成功sign喚醒 while (!isOnSyncQueue(node)) { // 阻塞掛起 LockSupport.park(this); // 確認是否需要中斷時就退出 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } // 在同步佇列中,那就按同步佇列的規則在佇列中用CAS競爭同步狀態 if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; // 清理已經不是CONDITION狀態的等待佇列節點 if (node.nextWaiter != null) unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); }
喚醒程序signal()
1.檢查呼叫signal時,是否當前執行緒獲取了鎖,不是則拋例外
if (!isHeldExclusively()) throw new IllegalMonitorStateException();
2.獲取condition佇列中的第一個等待節點
Node first = firstWaiter; if (first != null) doSignal(first);
3.用CAS清除CONDITION狀態
if (!node.compareAndSetWaitStatus(Node.CONDITION, 0)) return false;
4.呼叫AQS的enq(firstWaitNode),將這個節點放入到同步佇列的隊尾(需要CAS支撐?因為可能是共享的,即使獲取了鎖也需要競爭)
Node p = enq(node);
5.移動入同步佇列成功后(可能經歷了幾次CAS),再用unpark方法喚醒,那個執行緒就進入了上面代碼中Park之后的部分了
int ws = p.waitStatus; if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL)) LockSupport.unpark(node.thread);
6.如果是signalAll方法,則等待佇列中每個節點都執行一次signal方法,全部移入同步佇列中并喚醒(喚醒后他們很可能還會因為搶不到資源而阻塞,但佇列位置不同了,也無法再通過sign喚醒了)
do { Node next = first.nextWaiter; first.nextWaiter = null; transferForSignal(first); first = next; } while (first != null);
點擊關注,第一時間了解華為云新鮮技術~
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/526836.html
標籤:其他
上一篇:云資料庫時代,DBA將走向何方?
