上一篇文章介紹了 ReentrantLock 的 lock-unlock 流程,本篇將深入探討 Condition 的 await 與 signal 機制。
與釋放隊列(release queue)類似,Condition 隊列也是一個虛擬隊列,節點之間透過 nextWaiter 關聯。因為 Condition 節點必須轉為釋放節點才能解除阻塞,所以不需要 prevWaiter 指針。
整體流程大致如下:調用 await 的線程會進入 Condition 隊列;調用 signal 的線程會從 firstWaiter 開始尋找未取消的節點,將其移入釋放隊列;隨後當 signal 線程調用 await 或 unlock 時執行 release 方法,才有機會喚醒該節點。相比 lock-unlock,正常流程較簡單,但中斷處理更為複雜。
該過程可分為三個步驟。在阻塞線程前,需判斷 Condition 節點是否已在釋放隊列中。若已在隊列,則無需阻塞,可直接參與鎖競爭。關鍵邏輯見 isOnSyncQueue(Node node) 方法:
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null)
return true;
return findNodeFromTail(node);
}
signal 方法較簡單:從 firstWaiter 開始,找到首個未取消的節點加入釋放隊列。即使節點最初未取消,入隊時也可能被取消,因此代碼做了特殊處理。核心邏輯在 transferForSignal(Node node):
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
signal 僅修改節點狀態,並不直接喚醒線程。喚醒需待後續調用 await() 或 unlock() 時執行 release 方法,對釋放隊列中的節點解除阻塞。
signalAll 與 signal 類似,只是將 Condition 隊列中所有節點全部移入釋放隊列。中斷處理的詳細流程圖將在後續文章補充。
參考資料:怎麼理解 Condition