AQS
前言
上篇文章 中,我們分析了同步隊列的節點如何new出條件隊列,條件隊列節點又是如何跑到同步隊列中去的。
而在分析同步隊列的節點如何new出條件隊列的時候,我們從 await方法開始分析,直到了 isOnSyncQueue方法。而本篇內容將接着繼續分析,掛起的線程喚醒後的過程。
public final void await() throws InterruptedException {
// 如果當前線程被中斷過, 則直接拋出中斷異常
if (Thread.interrupted())
throw new InterruptedException();
// 封裝當前線程, 並扔到條件隊列中
Node node = addConditionWaiter();
// 完全釋放當前線程佔用的鎖, 並保存釋放前(即當前)的鎖狀態
int savedState = fullyRelease(node);
int interruptMode = 0;
// 如果當前節點(封裝好的線程)不在同步隊列中
// 說明還沒有被signal過
while (!isOnSyncQueue(node)) {
// 掛起
LockSupport.park(this);
// 講道理, 上面執行後不應該執行到這裏的
// 當如果線程被中斷了、或者被signal了,
// 則會跑到這裏
// 檢查換新的原因, 如果是中斷則跳出循環
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 下面這部分代碼先不用管
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
阻塞的線程醒過來了
當條件隊列的節點不在同步隊列時,我們接下的操作是計劃將當前線程掛起,既然掛起了,下面的程序又是什麼時候執行呢?
while (!isOnSyncQueue(node)) {
// 掛起
LockSupport.park(this);
// 講道理, 上面執行後不應該執行到這裏的
// 當如果線程被中斷了、或者被signal了,
// 則會跑到這裏
// 檢查喚醒的原因, 如果是中斷則跳出循環
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
這裏有兩種情況:
1.沒有中斷、有對應的ConditionObject調用了signal方法進行喚醒操作。
2. 休眠的線程被中斷了、被迫喚醒。
checkInterruptWhileWaiting
所以,這裏既然醒了,我們需要檢查一下醒來的原因,以便判斷接下的路該如何走。於是就有了這一行代碼的判斷:
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
關於checkInterruptWhileWaiting方法的源碼如下,它會判斷當前線程是否發生中斷,沒有的話取值爲0(被正常的signal),有的話,需要檢查一下中斷的原因。
/**
* Checks for interrupt, returning THROW_IE if interrupted
* before signalled, REINTERRUPT if after signalled, or
* 0 if not interrupted.
*/
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ?
THROW_IE : REINTERRUPT) : 0;
}
interruptMode = 0
先說說interruptMode 爲0的情況吧,如果爲0,沒有中斷,繼續外層 的判斷,即while (!isOnSyncQueue(node))
爲什麼需要上面的判斷呢?根據我們前面對signal方法的分析,喚醒的線程可能還沒來得及跑到同步隊列中,也就是說線程可能處於“假喚醒”的狀態,處理的方式也比較簡單,就加上while (!isOnSyncQueue(node))的循環判斷,正常醒來的線程沒有在同步隊列中,那就仍然在等待隊列中,繼續讓它睡覺。
如果已經在同步隊列中了,跳出while循環、然後執行如下部分的源碼,前面分析acquireQueued時候,我們可以知道它就是 阻塞式的獲取鎖。
如果acquireQueued返回false表示沒有發生中斷,如果返回true則表示在搶鎖的過程中發生了中斷。當然了,由於interruptMode == 0這裏interruptMode = REINTERRUPT會被執行到。
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
接着判斷如果node.nextWaiter不爲null,會清除已經處於Cancel狀態的節點。最後,在源碼中進行自我中斷。
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
interruptMode != 0
interruptMode != 0表示有中斷髮生,根據如下源碼的註釋,我們可以知道,中斷也是需要分爲兩種情況的,一種是在喚醒signal之前發生中斷,此時將interruptMode 設置爲THROW_IE ,另一種則是在signal之後(已發生signal)發生中斷,此時將interruptMode 設置爲REINTERRUPT 。
這裏可能比較難理解,很多人覺得如果是signal後,那麼Thread.interrupted() ?判斷時不應該返回0嗎。其實這種思考陷入了一個單線程的思維模式裏。多線程之下,A調用signa和B發生中斷,是兩個維度的東西,沒有商量的情況下,誰也不能保證B的中斷是發生在A的signal之前、還是之後。
關於interruptMode,先做一下總結,它初始值爲0,它的取值有三個:
-
REINTERRUPT:1 // 表示線程從等待退出後需要彌補一下中斷
-
THROW_IE :-1 // 表示線程從等待退出後需要拋出中斷
-
0:沒有中斷
😃 說的啥意思啊????
/* Mode meaning to reinterrupt on exit from wait */
private static final int REINTERRUPT = 1;
/* Mode meaning to throw InterruptedException on exit from wait */
private static final int THROW_IE = -1;
transferAfterCancelledWait
爲了回答上述問題,我們可以接着看transferAfterCancelledWait方法,首選,能進入transferAfterCancelledWait這個方法,說明前面的Thread.interrupted()的判斷爲true,表示發生了中斷,但發生中斷後,如何判斷中斷髮生在signal之前、還是之後呢?
如果是在signal之前發生的中斷,那麼肯定還沒進入同步隊列。那麼我們是否可以通過判斷同步隊列是否存在該節點來達到目的呢?
我覺得可以,不過這種方法複雜度得O(n)吧~而源碼中通過compareAndSetWaitStatus方法(CAS操作)試圖將CONDITION狀態置爲0,如果成功,說明是在signal之前發生的中斷(signal之後狀態已經被修改了)
/**
* Transfers node, if necessary, to sync queue after a cancelled wait.
* Returns true if thread was cancelled before being signalled.
*
* @param node the node
* @return true if cancelled before the node was signalled
*/
final boolean transferAfterCancelledWait(Node node) {
// 如果能通過CAS成功將等待狀態置爲0, 說明中斷在signal之前
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
// 進入到同步隊列中
enq(node);
return true;
}
/*
* If we lost out to a signal(), then we can't proceed
* until it finishes its enq(). Cancelling during an
* incomplete transfer is both rare and transient, so just
* spin.
*/
// 執行到這裏說明上面的CAS不成功
// 說明中斷髮生在signal之後
while (!isOnSyncQueue(node))
// 線程讓步
Thread.yield();
return false;
}
既然CAS不成功,說明中斷髮生在signal之後,但我還是得 while (!isOnSyncQueue(node))再判斷一下,前面已經說了,signal發生時,不是立刻就到達同步隊列的,如果同步隊列沒有,Thread.yield()這裏可以理解爲先讓一下步,緩一緩,等節點到了同步隊列,我再返回。
await
transferAfterCancelledWait方法分析完畢,再回到await方法下面這一行代碼,此時interruptMode 就不會爲0了,即跳出while循環了。
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
此時繼續往下,執行這部分代碼,當interruptMode爲THROW_IE,表示signal之前發生的中斷,此時在中斷報告中拋出了異常。如果爲REINTERRUPT,我們在報告中進行自我中斷。這一點和我們分析acquire的時候有點兒像。
究其根本原因都是Thread.interrupted()並不會真的中斷,只是進行判斷、返回結果並清除中斷標識。真正是否中斷交由頂層接口進行操作。
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
總結
本文主要分析await後掛起的線程醒來時,接下來的運行流程。由於醒來時,程序並不知道原因所以需要進行判斷。
而醒來的原因又可以分爲signal自然喚醒、中斷(被迫喚醒)。其中,中斷導致的喚醒,又可以分爲signal前中斷和signal後中斷,針對這些情況,本文都做了分析。
(完結)