源碼分析AbstractQuenedSynchronized(一)
源碼分析AbstractQuenedSynchronized(三)
主要分析AbstractQuenedSynchronized的Condition接口實現類ConditionObject,包括await方法和signal方法
Condition
Condition的產生通過ReentrantLock實例的newCondition方法,而實現ReentrantLock功能的內部類Sync又是AbstractQuenedSynchronized的實現類,由此可以看出:Condition依賴於ReentrantLock實現,而ReentrantLock又依賴於AbstractQuenedSynchronized實現.
//##ReentrantLock類
final ConditionObject newCondition() {
//實例化一個ConditionObject
return new ConditionObject();
}
//##AbstractQuenedSynchronized類
public class ConditionObject implements Condition, java.io.Serializable {
/** 條件隊列的第一個結點 */
private transient Node firstWaiter;
/** 條件隊列的最後一個結點 */
private transient Node lastWaiter;
public ConditionObject() { }
}
由Condition的實現類ConditionObject我們可以看出由兩個屬性:firstWaiter和lastWaiter,它們都是Node。
在上一篇介紹 AQS 的時候,我們有一個阻塞隊列(sync queue),用於保存等待獲取鎖的線程的隊列。這裏我們引入另一個概念,叫條件隊列(condition queue),如下圖所示。
圖片源於https://javadoop.com/post/AbstractQueuedSynchronizer-2
從圖中,我們首先需要了解的是:
- 條件隊列和阻塞隊列的結點都是Node類型,因爲條件隊列的結點是要轉移到阻塞隊列中的
- Condition的實例是通過ReentrantLock的newCondition方法產生的,一個newCondition產生一個新的Condition實例
- 每一個Condition實例都有一個關聯的條件隊列,由上面的ConditionObject 類我們也可以看出,只有兩個屬性:條件隊列的頭結點和末尾結點。如線程 1 調用 condition1.await() 方法即可將當前線程 1 包裝成 Node 後加入到條件隊列中,然後阻塞在這裏,不繼續往下執行,條件隊列是一個單向鏈表
- 調用condition1.signal() 觸發一次喚醒,此時喚醒的是隊頭,會將condition1 對應的條件隊列的 firstWaiter(隊頭) 移到阻塞隊列的隊尾,等待獲取鎖,獲取鎖後 await 方法才能返回,繼續往下執行。
await方法
//##############AbstractQuenedSynchronized類中的ConditionObject類########################
public final void await() throws InterruptedException {
//1.首先判斷線程狀態是否中斷
if (Thread.interrupted())
//中斷則直接拋異常
throw new InterruptedException();
//2.將當前線程封裝成狀態爲CONDITION的Node並添加到條件隊列隊尾
Node node = addConditionWaiter();
/*======================================================================
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
// 將隊列中狀態不爲CONDITION的結點移除
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
======================================================================*/
//3.調用await方法之前,當前線程必須要持有鎖,然後釋放鎖,這裏要完全釋放獨佔鎖
int savedState = fullyRelease(node);//savedState爲釋放鎖之前的state
/*======================================================================
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();//返回的savedState爲釋放鎖之前的state
//釋放鎖
//如果一個線程沒有獲得鎖(沒有執行lock方法)就執行condition.await()方法,該線程結點會進入等待隊列然後執行release方法拋出異常,同時node狀態變爲cancelled,這個已經入隊的結點在後面會被後續結點“請出去”
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
//當釋放鎖失敗時
node.waitStatus = Node.CANCELLED;
}
}
======================================================================*/
int interruptMode = 0;
//4. 當線程結點不在阻塞隊列時將線程掛起
/*退出當前循環有兩種情況:一、當前線程node轉移到阻塞隊列中 二、線程中斷*/
while (!isOnSyncQueue(node))
/*======================================================================
// 當一個node從條件隊列轉移到阻塞隊列時返回true
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//有後繼結點,說明一定在阻塞隊列
if (node.next != null) // If has successor, it must be on queue
return true;
//不能通過node.prev!=null來判斷node在阻塞隊列,因爲CAS操作將自己設爲新的tail過程中可能會失敗,因此該方法findNodeFromTail從tail往前遍歷看能不能在阻塞隊列中找到該node
return findNodeFromTail(node);
}
======================================================================*/
{
//當前線程結點不在阻塞隊列中,當前線程掛起
LockSupport.park(this);
/*========================
5 有以下三種情況會讓 LockSupport.park(this); 這句返回繼續往下執行:
(1)常規路徑。signal -> 轉移節點到阻塞隊列 -> 獲取了鎖(unpark)
(2)線程中斷。在 park 的時候,另外一個線程對這個線程進行了中斷
(3)signal 的時候我們說過,轉移以後的前驅節點取消了,或者對前驅節點的CAS操作失敗了
==========================*/
//邏輯走到這裏,說明線程被喚醒,檢查中斷狀態
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
/*======================================================================
interruptMode 可以取值爲 REINTERRUPT(1),THROW_IE(-1),0
// 1. 如果在 signal 之前已經中斷,返回 THROW_IE
// 2. 如果是 signal 之後中斷,返回 REINTERRUPT
// 3. 沒有發生中斷,返回 0
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
final boolean transferAfterCancelledWait(Node node) {
//由於Signal操作將node的WaitStatus設爲0,所以如果下面CAS成功則說明中斷髮生在signal方法前;CAS失敗說明發生在signal方法後
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
//即使中斷,也會轉移到阻塞隊列
//這裏描繪了一個場景,本來有個線程,它是排在條件隊列的後面的,但是因爲它被中斷了,那麼它會被喚醒,然後它發現自己不是被 signal 的那個,但是它會自己主動去進入到阻塞隊列。
enq(node);
return true;
}
//邏輯走到這裏說明中斷髮生在signal方法後,但是signal方法可能還沒有結束,因此這邊自旋等待其完成
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
======================================================================*/
//6.被喚醒後當前線程node進入阻塞隊列,等待獲取鎖
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
//acquireQueued(node, savedState) 的返回值就是代表線程是否被中斷。邏輯到這裏,說明線程在signal方法後發生中斷
interruptMode = REINTERRUPT;//將狀態設爲REINTERRUPT,用於待會重新中斷
if (node.nextWaiter != null) // clean up if cancelled
//signal方法會將結點從條件隊列轉移到阻塞隊列,同時斷開node和後面結點的聯繫,邏輯走到這裏,說明signal之前發生了中斷,也需要將節點進行轉移到阻塞隊列,這部分轉移的時候,是沒有設置 node.nextWaiter = null 的。
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
/*======================================================================
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
//THROW_IE則拋異常,REINTERRUPT則重新中斷(這裏中斷的含義是設置一個線程的中斷狀態爲true,並沒有拋異常!)
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();// Thread.currentThread().interrupt();
}
======================================================================*/
}
注意到上述await方法的步驟5有三種情況會讓 LockSupport.park(this)方法返回,除了響應中斷就是利用正常途徑signal方法了,下面開始分析Signal方法
signal方法
//##承接await方法的步驟4:LockSupport.park(this);
//喚醒等待最久的線程,將這個線程node從條件隊列中轉移到阻塞隊列中
public final void signal() {
// 調用 signal 方法的線程必須持有當前的獨佔鎖
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
/*======================================================================
private void doSignal(Node first) {
do {
//將firstWaiter 指向下一個Waiter,因爲它就要轉移到阻塞隊列了
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
//first即將離開,斷開和後面的關係
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);//這裏 while 循環,如果 first 轉移不成功,那麼選擇 first 後面的第一個節點進行轉移,依此類推
}
//返回return代表:成功將node轉移到阻塞隊列 false:該節點已經取消,不需要轉移了
final boolean transferForSignal(Node node) {
// CAS 如果失敗,說明此 node 的 waitStatus 已不是 Node.CONDITION,說明節點已經取消,
// 既然已經取消,也就不需要轉移了,方法返回,轉移後面一個節點
// 否則,將 waitStatus 置爲 0
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node);//自旋進入阻塞隊列的隊尾,p爲node在阻塞隊列的前驅結點
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
//正常情況下不會進入該邏輯,但是如果前驅結點取消或者CAS設置前驅結點爲SINGAL失敗則喚醒當前線程,直接進入步驟4的下一步
LockSupport.unpark(node.thread);
return true;
}
======================================================================*/
總結
Conditiond的await和signal過程:
await
1.首先判斷線程狀態是否中斷
2.將當前線程封裝成狀態爲CONDITION的Node並添加到條件隊列隊尾
3.釋放鎖,這裏要完全釋放獨佔鎖(調用await方法之前,當前線程必須要持有鎖,否則在嘗試釋放鎖時就會拋異常)
4. 當線程結點不在阻塞隊列時將線程掛起
5.signal方法將這個線程node從條件隊列中轉移到阻塞隊列中(轉移到阻塞隊列的方式有兩個:signal方式和中斷方式)
6.在阻塞隊列中等待重新獲取鎖