文章目錄
- 前言
- 與wait/notify進行對比
- 同步隊列 和 條件隊列
- CondtionObject
- await()第一次調用park之前
- await()第一次調用park之後
- signalAll流程
- signal流程
- transferForSignal假設不喚醒node線程
- transferForSignal假設喚醒node線程
- 中斷流程(有線程將node線程中斷,在signal之前)
- 因中斷被喚醒的node線程 和 signal線程 的競爭關係
- 終於執行到acquireQueued
- await()總結
- awaitUninterruptibly()
- awaitNanos(long nanosTimeout)
- await(long time, TimeUnit unit)
- awaitUntil(Date deadline)
前言
一個新設計的出現,總是爲了替換現有的略有不足的設計。而Condition接口的出現,是爲了代替監視器鎖的wait/notify機制,提供更強大的功能。
與wait/notify進行對比
我們將Object
自帶的wait/notify
方法與Condition
接口提供的await/signal
進行一個對比。
Object方法 | Condition方法 | 區別 |
---|---|---|
void wait() | void await() | |
void wait(long timeout) | long awaitNanos(long nanosTimeout) | 時間單位:前者毫秒ms,後者納秒ns 返回值 |
void wait(long timeout, int nanos) | boolean await(long time, TimeUnit unit) | 時間單位:前者只能納秒,後者什麼單位都可以 返回值 |
void notify() | void signal() | |
void notifyAll() | void signalAll() | |
- | void awaitUninterruptibly() | Condition獨有 |
- | boolean awaitUntil(Date deadline) | Condition獨有 |
先簡單說明一下它們之間的相同之處,以便之後更好地理解Condition
接口的實現:
- 調用
wait()
的線程必須已經處於同步代碼塊中,換言之,調用wait()
的線程已經獲得了監視器鎖;調用await()
的線程則必須是已經獲得了lock鎖。 - 執行
wait()
時,當前線程會釋放已獲得的監視器鎖,進入到該監視器的等待隊列中;執行await()
時,當前線程會釋放已獲得的lock鎖,然後進入到該Condition的條件隊列中。 - 退出
wait()
時,當前線程又重新獲得了監視器鎖;退出await()
時,當前線程又重新獲得了lock鎖。 - 調用監視器的
notify
,會喚醒等待在該監視器上的線程,這個線程此後才重新開始鎖競爭,競爭成功後,會從wait
方法處恢復執行;調用Condition的signal
,會喚醒等待在該Condition上的線程,這個線程此後才重新開始鎖競爭,競爭成功後,會從await
方法處恢復執行。
同步隊列 和 條件隊列
對於每個Condition對象來說,都對應到一個條件隊列condition queue
。而對於每個Lock對象來說,都對應到一個同步隊列sync queue
。
sync queue
獨佔鎖的獲取過程中,我們提到了,每個線程在lock()
嘗試獲取鎖失敗後,都會被包裝成一個node放到sync queue
中去。
sync queue
是一個雙向鏈表,它使用prev
和next
作爲鏈接。在這個隊列中,我們幾乎不關心節點的nextWaiter
成員,最多會在共享鎖模式下,用來標識節點是否爲共享鎖節點。隊頭是一個dummy node即Thread成員爲null,第一個等待線程永遠只能是head的後繼。
condition queue
每一個Condition對象都對應到一個條件隊列condition queue
,而每個線程在執行await()
後,都會被包裝成一個node放到condition queue
中去。
condition queue
是一個單向鏈表,它使用nextWaiter
作爲鏈接。這個隊列中,不存在dummy node,每個節點都代表一個線程。這個隊列的節點的狀態,我們只關心狀態是否爲CONDITION
,如果是CONDITION
的,說明線程還等待在這個Condition對象上;如果不是CONDITION
的,說明這個節點已經前往sync queue
了。
二者的關係
假設現在存在一個Lock對象和通過這個Lock對象生成的若干個Condition對象,從隊列上來說,就存在了一個sync queue
和若干個與這個sync queue
關聯的condition queue
。本來這兩種隊列上的節點沒有關係,但現在有了signal方法,就會使得condition queue
上的節點會跑到sync queue
上去。
上圖簡單體現了節點從從condition queue
轉移到sync queue
上去的過程。即使是調用signalAll
時,節點也是一個一個轉移過去的,因爲每個節點都需要重新建立sync queue
的鏈接。
我們這裏可以先簡單理解一下關於隊列的動作:
- 如果一個節點剛入隊
sync queue
,說明這個節點的代表線程沒有獲得鎖(嘗試獲得鎖失敗了)。 - 如果一個節點剛出隊
sync queue
(指該節點的代表線程不在同步隊列中的任何節點上,因爲它已經跑到了AQS的exclusiveOwnerThread
成員上去了),說明這個節點的代表線程剛獲得了鎖(嘗試獲得鎖成功了)。 - 如果一個節點剛入隊
condition queue
,說明這個節點的代表線程此時是有鎖了,但即將釋放。 - 如果一個節點剛出隊
condition queue
,因爲前往的是sync queue
,說明這個節點的代表線程此時是沒有獲得鎖的。
CondtionObject
對於ReentrantLock來說,我們使用newCondition
方法來獲得Condition接口的實現,而ConditionObject
就是一個實現了Condition接口的類。
//ReentrantLock.java
public class ReentrantLock implements Lock, java.io.Serializable {
public Condition newCondition() {
return sync.newCondition();
}
abstract static class Sync extends AbstractQueuedSynchronizer {
final ConditionObject newCondition() {
return new ConditionObject();
}
}
}
而ConditionObject
又是AQS的一個成員內部類,這意味着不管生成了多少個ConditionObject
,它們都持有同一個AQS對象的引用,這和“一個Lock可以對應到多個Condition”相吻合。這也意味着:對於同一個AQS來說,只存在一個同步隊列sync queue
,但可以存在多個條件隊列condition queue
。
成員內部類有一個好處,不管哪個ConditionObject
對象都可以調到同一個外部類AQS對象的方法上去。比如acquireQueued
方法,這樣,不管node在哪個condition queue
上,最終它們離開後將要前往的地方總是同一個sync queue
。
public abstract class AbstractQueuedSynchronizer{
private transient volatile Node head;
private transient volatile Node tail;
public class ConditionObject implements Condition {
private transient Node firstWaiter;
private transient Node lastWaiter;
}
}
firstWaiter
和lastWaiter
分別代表條件隊列的隊頭和隊尾。- 注意,
firstWaiter
和lastWaiter
都不再需要加volatile
來保證可見性了。這是因爲源碼作者是考慮,使用者肯定是以獲得鎖的前提下來調用await() / signal()
這些方法的,既然有了這個前提,那麼對firstWaiter
的讀寫肯定是無競爭的,既然沒有競爭也就不需要 CAS+volatile 來實現一個樂觀鎖了。
final Lock lock = new ReentrantLock();
Condition Emptycondition = lock.newCondition();
Emptycondition.await(); //這樣會拋出異常
現在考慮沒有這個前提。上面代碼在沒有獲得鎖的情況就去調用了await
,會導致await
拋出異常,但是在拋出異常之前肯定會調用到addConditionWaiter
,而addConditionWaiter
有對這兩個變量的讀寫,現在可能同時有兩個線程對非volatile變量進行讀寫,也就可能造成問題。所以,在用戶使用不規範的情況下,還是有可能造成變量讀寫競爭,且沒有鎖保護的情況。(函數實現後面會講,請接着看)
public ConditionObject() { }
但ConditionObject的構造器什麼也不做。
await()第一次調用park之前
public final void await() throws InterruptedException {
// 在調用await之前,當前線程就已經被中斷了,那麼拋出異常
if (Thread.interrupted())
throw new InterruptedException();
// 將當前線程包裝進Node,然後放入當前Condition的條件隊列
Node node = addConditionWaiter();
// 釋放鎖,不管當前線程重入鎖多少次,都要釋放乾淨
int savedState = fullyRelease(node);
int interruptMode = 0;
// 如果當前線程node不在同步隊列上,說明還沒有別的線程調用 當前Condition的signal。
// 第一次進入該循環,肯定會符合循環條件,然後park阻塞在這裏
while (!isOnSyncQueue(node)) {
LockSupport.park(this); // 將阻塞在這裏
// 如果被喚醒,要麼是因爲別的線程調用了signal使得當前node進入同步隊列,
// 進而當前node等到自己成爲head後繼後並被喚醒。
// 要麼是因爲別的線程 中斷了當前線程。
// 如果接下來發現自己被中斷過,需要檢查此時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);
}
註釋介紹了大概的過程,但首先要明確會有哪些線程在執行:
- 執行
await
的當前線程。這個線程是最開始調用await
的線程,也是執行await
所有調用鏈的線程,它被包裝進局部變量node
中。(後面會以node線程來稱呼它) - 執行
signal
的線程。這個線程會改變await
當前線程的node的狀態,使得await
當前線程的node前往同步隊列,並在一定條件在喚醒await
當前線程。 - 中斷
await
當前線程的線程。你就當這個線程只是用來喚醒await
當前線程,並改變其中斷狀態。 - 執行
unlock
的線程。如果await
當前線程的node已經是同步隊列的head後繼,那麼獲得獨佔鎖的線程在釋放鎖時,就會喚醒await
當前線程。
理解了這幾個線程的存在,對於本文的理解有很大幫助。從用戶角度來說,執行await \ signal \ unlock
的前提都是線程必須已經獲得了鎖。
addConditionWaiter
private Node addConditionWaiter() {
Node t = lastWaiter;//獲得隊尾
// 同步隊列中的節點只是CONDITION的,就認爲是以後將離開條件隊列的節點。
// 將調用unlinkCancelledWaiters來一次大清理,並重新獲得隊尾
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
// 包裝當前線程爲node,狀態初始爲CONDITION
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)// 如果當前隊尾爲null,那麼整個條件隊列都沒初始化呢
firstWaiter = node; //把新建node作爲隊頭
else // 如果隊列有至少一個節點
t.nextWaiter = node; // 把新建node接在隊尾後面
lastWaiter = node; //讓node成爲新隊尾
return node;
}
上面函數描述了新建node加入到條件隊列中的過程,我們和獨佔鎖獲取過程中新建node加入同步隊列進行對比:
- 同步隊列
sync queue
的新建node,它的初始狀態爲0。而條件隊列condition queue
的新建node的初始狀態爲CONDITION
。 sync queue
如果擁有隊頭,隊頭肯定會是一個dummy node(即線程成員爲null)。condition queue
則不會有一個dummy node,每個節點的線程成員都不爲null。sync queue
是一個雙向鏈表,需要維持前驅和後繼都正確。condition queue
只是一個單鏈表,只需要維持後繼即可。
這裏先提前說下,中斷await
當前線程的線程(這裏特指中斷操作在signal
之前)和執行signal
的線程都會使得條件隊列上的node的狀態從CONDITION
變成0。
unlinkCancelledWaiters
unlinkCancelledWaiters
函數是用來從頭到尾清理狀態不爲CONDITION
的節點的。
private void unlinkCancelledWaiters() {
Node t = firstWaiter; //獲得隊頭
Node trail = null; //trail用來保存遍歷過程中,最近一次發現的狀態爲CONDITION的節點
while (t != null) { //只要循環變量不爲null,循環繼續
Node next = t.nextWaiter; //得到循環變量的後繼,循環結束前使用
// 如果循環變量不爲CONDITION
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null; //首先使得t與後繼斷開鏈接
// 如果直到當前循環都還沒有發現一個CONDITION的節點
if (trail == null)
firstWaiter = next;//那麼將循環變量的後繼,作爲新隊頭
// 如果當前循環之前已經發現了一個CONDITION的節點
else
trail.nextWaiter = next;//那麼將trail與next相連,相當於跳過了循環變量t
// 如果已經遍歷到隊尾,需要將trail作爲隊尾,因爲trail纔是隊列中最後一個爲CONDITION的節點
if (next == null)
lastWaiter = trail;
}
// 如果循環變量爲CONDITION,則更新trail
else
trail = t;
t = next; //循環結束前,用next更新循環變量
}
}
主要是一些單鏈表的操作,trail
變量很重要,它用來保存遍歷過程中,最近一次發現的狀態爲CONDITION的節點。
fullyRelease
在調用fullyRelease
之前,當前線程已經被包裝成node放到條件隊列中去了。注意,在這個函數以後,我們再也不會對firstWaiter
和lastWaiter
輕舉妄動了,因爲以後的執行過程中,當前線程很可能是沒有持有鎖的。
我們調用fullyRelease
函數來釋放當前線程佔有的鎖。
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;
}
}
該函數很簡單,只是通過調用獨佔鎖釋放過程中的release
函數來釋放鎖,注意,不管鎖被重入了幾次,在這裏我們都會一次性釋放乾淨的(release(savedState)
)。這也是爲什麼這個函數叫做fullyRelease全釋放。
如果真的有哪個傻子在沒有獲得鎖時,調用了await
,那麼release
會拋出異常且導致拋出時failed
變量爲真,那麼在finally塊裏面就會執行語句,把當前線程的node的狀態變成非CONDITION
的。
isOnSyncQueue
在執行完fullyRelease
後的這一段時間裏,當前線程是沒有持有鎖的了,因爲鎖已經被自己給釋放了。更重要的是,接下來的這一段時間裏,另一個線程可能又獲得了鎖,然後開始執行await \ signal \ unlock
,即接下來得考慮多線程了。
回到await的邏輯,現在要進入循環了。循環裏馬上就會調用LockSupport.park(this);
阻塞當前線程,這也就是本章大標題說的“await()第一次調用park之前”的時間點了。
但是每次循環都會判斷一下循環條件!isOnSyncQueue(node)
,即當前線程node不在同步隊列中。很明顯,如果是第一次進入循環,這個循環條件肯定會滿足的,因爲我們剛剛纔執行了addConditionWaiter
將當前線程node加入到條件隊列中呢。
這個循環條件!isOnSyncQueue(node)
主要是爲了當前線程被喚醒後,進行必要的判斷。
final boolean isOnSyncQueue(Node node) {
//如果node狀態爲CONDITION,或者雖然node狀態不爲CONDITION,但node前驅爲空
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//執行到這裏,說明上面兩個條件都不成立:
//1. node狀態不爲CONDITION 2. node前驅不爲空
//如果node後繼不爲空,說明已經在sync queue
if (node.next != null)
return true;
//執行到這裏,說明:
//前一個時間點檢測到,1. node狀態不爲CONDITION 2. node前驅不爲空
//後一個時間點檢測到:node後繼爲空
//現在發現node處於一個狀態:前驅不爲空,但後繼爲空。如果node是當前隊尾肯定也是這種狀態
//但enq進隊尾時CAS設置tail失敗時,也會是這種狀態。所以需要從尾到頭檢測一遍。
return findNodeFromTail(node);
}
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
注意,本函數都是在檢測prev next
兩個鏈接,即sync queue
的數據結構。
if (node.waitStatus == Node.CONDITION || node.prev == null)
分支:- 先看看進入分支的情況:
- 如果前者成立,即node狀態爲CONDITION。說明node代表線程還沒有前往同步隊列。
- 如果前者不成立,後者成立,即雖然node狀態不爲CONDITION,但node前驅爲空。一個節點如果已經入隊成功,那麼它的prev肯定不爲null。
- 再看看退出這個分支的情況:
- 中間條件是或,所以是二者都不成立。即1. node狀態不爲CONDITION 2. node前驅不爲空。但如果只是已知這些信息,則還需要繼續判斷。
- 先看看進入分支的情況:
if (node.next != null)
分支,如果說node的next都已經不爲空了,說明node成爲隊尾後,又有節點入隊成爲新隊尾。而發生這一切的前提則是,node已經成功入隊過了。- 最後需要從尾到頭遍歷,看是否能在同步隊列上找到node。執行
findNodeFromTail
之前,發現node處於一個狀態:前驅不爲空,但後繼爲空。如果node是當前隊尾肯定也是這種狀態。但enq進隊尾時CAS設置tail失敗時,也會是這種狀態。所以需要從尾到頭檢測一遍。
關於狀態不爲CONDITION,前面有說過,有兩種線程可以使得條件隊列上的node的狀態從CONDITION
變成0。但現在可以排除中斷await
當前線程的線程這種情況(之後具體介紹這個流程),因爲如果發生了中斷,await的while循環直接就會break出循環了(if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break;
),也就不會執行到isOnSyncQueue
函數了。
簡單的說,isOnSyncQueue
判斷節點已經入隊同步隊列的標準,必須是node已經成爲隊尾(包括當前是隊尾,或者曾經是隊尾)。
await()第一次調用park之後
正常情況下,await()
第一次調用park之後,就會阻塞在這裏了,所以這裏必須依靠別的線程出來救場了。
此時node的狀態爲:
- 在數據結構上,node在條件隊列上(
addConditionWaiter
)。 - 在執行過程上,node線程當前阻塞在
LockSupport.park(this)
這裏。
signalAll流程
前面我們提前說過執行signal
的線程,我們先來看看執行signalAll的線程會幹什麼。
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
首先會檢查執行signalAll的線程是否已經獲得了鎖,通過判斷ExclusiveOwnerThread
成員變量。然後判斷條件隊列是否爲空,只要不爲空就執行doSignalAll
。
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}
上來就把代表條件隊列的隊頭隊尾成員置null,之後別人就無法通過隊頭隊尾找到隊列中的節點了,只有當前線程能通過局部變量first
來找到隊列節點了。
而接下來不斷遍歷,直到已經遍歷到隊尾(first != null
)。每次遍歷中,將當前遍歷節點 與 剩下的條件隊列鏈 斷開,然後對當前遍歷節點執行transferForSignal
。
final boolean transferForSignal(Node node) {
/*
* 如果失敗,說明node代表線程因中斷而已經執行了中斷流程中的compareAndSetWaitStatus(node, Node.CONDITION, 0)
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* 執行enq將node入隊sync queue,enq返回node的前驅。
*/
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
transferForSignal
函數簡單的說,就是爲讓參數node入隊,如果入隊成功就返回true。
- 如果CAS設置node狀態從CONDITION變成0失敗了,說明node代表線程因中斷而已經執行了中斷流程中的
compareAndSetWaitStatus(node, Node.CONDITION, 0)
。這也間接說明了signal流程和中斷流程都是以 成功設置node狀態 作爲標準,哪個流程成功了,哪個流程就把node入隊同步隊列,從而以免重複入隊。(這一點需要和後面的中斷流程內容聯動,如果難以理解,可以直接往下看) - 如果CAS設置成功,那麼
enq(node)
入隊,然後肯定返回true。但是注意,在一定條件下,會喚醒node代表線程。注意enq(node)
返回node入隊後的前驅prev
。- 這個一定條件是指,node的前驅狀態是同步隊列節點的取消狀態,或者狀態<=0但CAS設置前驅狀態爲SIGNAL失敗了。
- 如果上面條件發生了,就直接喚醒node線程。這裏我們回想一下
await()
第一次調用park之後這個時間點,現在node線程終於被喚醒,假設沒有中斷髮生過的話,不會因break退出循環,再一次檢測!isOnSyncQueue(node)
會發生條件不成立,因爲node已經因爲enq(node)
而成功入隊。然後又會走到獨佔鎖獲得過程中的acquireQueued
函數。 - 喚醒node代表線程不一定代表它接下來能夠獲得鎖,但是我們也不用擔心這會有什麼壞影響,因爲
acquireQueued
函數自己會去做判斷,如果發現還是獲取不到鎖的話,則會調用shouldParkAfterFailedAcquire
將node的前驅設置爲SIGNAL的。 - 總之,
compareAndSetWaitStatus(p, ws, Node.SIGNAL)
直接保證了node的前驅狀態爲SIGNAL,而LockSupport.unpark(node.thread)
間接保證了node的前驅狀態爲SIGNAL,之所以說間接,是因爲這不是在signal線程裏做的,而是通過喚醒node線程做到的。
簡單總結一下signalAll
方法:
- 將條件隊列清空(通過
lastWaiter = firstWaiter = null
來達到效果,但函數中的局部變量已經保存了隊頭,且實際上節點的鏈接還存在着)。 - 遍歷每個節點。
- 如果遍歷節點已經被取消掉了(
compareAndSetWaitStatus(node, Node.CONDITION, 0)
失敗),那麼直接返回,處理下一個節點。 - 如果遍歷節點還沒取消掉(
compareAndSetWaitStatus(node, Node.CONDITION, 0)
成功),那麼將其入隊同步隊列。在一定條件下(無法設置node前驅狀態爲SIGNAL時),還將喚醒node代表線程。然後處理下一個節點。
另外注意,signalAll
方法直到結束返回,都一直沒有釋放鎖呢(因爲沒有在signalAll
裏面執行過release
),也就是說,執行signalAll
的線程一直都是持有鎖的。
signal流程
相比signalAll
,signal
方法只會喚醒一個node,準確的說,是喚醒從同步隊列隊頭開始的第一個狀態爲CONDITION
的node。
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first); //與之前的doSignalAll不同,這裏是doSignal
}
首先看調用signal
的線程是否已經擁有了獨佔鎖,不然就會拋出異常。與之前的doSignalAll
不同,這裏調用的是doSignal
。
private void doSignal(Node first) {//first參數是隊頭
do {
// 更新隊頭,讓隊頭往後移動一位
if ( (firstWaiter = first.nextWaiter) == null)
//如果發現新隊頭爲null,讓隊尾也爲null
lastWaiter = null;
// 老套路,把參數和後面的鏈表斷開
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null); // transferForSignal失敗纔會執行這行
// 獲得新隊頭,如果新隊頭不爲null,則繼續下一次循環,因爲現在一次signal都沒有成功呢
}
- 整個邏輯是一個
do while
循環,不過這個循環一遍不會執行多次,只要有一次signal成功了(transferForSignal(first)
返回了真)就不會繼續循環了,因爲這個函數的目的就只是signal一個節點。 - 如果signal失敗了,那麼獲得新隊頭(
(first = firstWaiter) != null
),只要新隊頭不爲null,則繼續下一次循環。 - 相比
signalAll
流程,我們使用的還是這個transferForSignal
來做的signal動作,但這裏我們終於使用到了transferForSignal
的返回值。 - 總之,
signal
方法會喚醒條件隊列中第一個狀態不是取消狀態(不是CONDITION
)的節點。
transferForSignal假設不喚醒node線程
按照上面signal
的流程(包括signal
和signalAll
),假設之前node線程沒有被中斷過,且執行signalAll
的線程不喚醒node線程,那麼執行signal流程完畢後此時node的狀態爲:
- 在數據結構上,node已經離開了條件隊列(
first.nextWaiter = null
),處於了同步隊列上了(Node p = enq(node)
)。 - 在執行過程上,node線程當前還是阻塞在
LockSupport.park(this)
這裏。(這一點沒有變化)
要想node線程執行完await()
方法,得需要執行unlock
的線程出馬了。當node已經成爲了head後繼,且獲得獨佔鎖的線程開始執行unlock
釋放鎖,將會喚醒node線程。node線程從LockSupport.park(this)
處喚醒後,不會因爲有中斷狀態而break出循環(假設沒有被中斷過,先不用看checkInterruptWhileWaiting
的實現,這裏只需要知道interruptMode
還是會保持爲0就行),然後判斷循環條件!isOnSyncQueue(node)
發現不成立而退出循環,然後將執行acquireQueued(node, savedState)
,但也不一定能獲得鎖,如果不能獲得,自然還是阻塞在acquireQueued
的shouldParkAfterFailedAcquire
裏。
transferForSignal假設喚醒node線程
按照上面signal
的流程,假設之前node線程沒有被中斷過,且執行signalAll
的線程喚醒node線程,那麼執行signal流程完畢後此時node的狀態爲:
- 在數據結構上,node已經離開了條件隊列(
first.nextWaiter = null
),處於了同步隊列上了(Node p = enq(node)
)。 - 在執行過程上,node線程從
LockSupport.park(this)
這裏被喚醒,不會因爲有中斷狀態而break出循環,然後判斷循環條件!isOnSyncQueue(node)
發現不成立而退出循環,然後執行acquireQueued
。如果不能獲得鎖,還是會阻塞在acquireQueued
的shouldParkAfterFailedAcquire
裏。
要想node線程執行完await()
方法,還是得需要執行unlock
的線程出馬。它執行unlock
後,node線程從acquireQueued
的shouldParkAfterFailedAcquire
處被喚醒,然後再一次去獲得鎖。但也不一定能獲得鎖,如果不能獲得,自然還是阻塞在acquireQueued
的shouldParkAfterFailedAcquire
裏。
中斷流程(有線程將node線程中斷,在signal之前)
中斷await
當前線程的線程終於出馬了(現在考慮沒有執行signal
的線程,或者說中斷這個node在signal這個node之前)。假設之前node線程有被中斷過,且在signal之前,看看此時是怎樣的流程。首先要知道,中斷await
當前線程的線程執行完中斷動作後,我們就不用關心它了,剩餘動作還是靠node線程自己完成的。
本場景下,中斷來臨之前,node的狀態就和 await()
第一次調用park之後 一樣:
- 在數據結構上,node在條件隊列上(
addConditionWaiter
)。 - 在執行過程上,node線程當前阻塞在
LockSupport.park(this)
這裏。
首先,中斷來了以後,node線程會從LockSupport.park(this)
處被喚醒,然後執行checkInterruptWhileWaiting
(之前一直沒有講這個函數,是因爲在這個流程中它纔會真正發揮作用,之前的signal流程它肯定會返回0的,而返回就不會break出循環)。
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
Thread.interrupted()
首先判斷當前線程有沒有被中斷過,如果沒有,那麼返回0.- 如果有中斷過,那麼通過
transferAfterCancelledWait(node)
判斷返回THROW_IE
還是REINTERRUPT
.
checkInterruptWhileWaiting
的返回值最終是會賦值給局部變量interruptMode
的,它現在有三種可能值:
0
:代表整個await
過程中沒有發生過中斷。THROW_IE
:代表await
執行完畢返回用戶代碼處時,需要拋出異常。當中斷流程發生在signal流程之前時。REINTERRUPT
:代表await
執行完畢返回用戶代碼處時,不需要拋出異常,僅需要重新置上中斷狀態。當signal流程發生在中斷流程之前時。
之所以THROW_IE
和REINTERRUPT
兩個值所代表的場景需要進行區分,是因爲一個線程A因await
而進入condition queue
後,正常的流程是另一個線程B執行signal
或signalAll
後才使得線程A的node入隊到sync queue
。但如果中斷流程發生在signal流程之前,也能使得線程A的node入隊到sync queue
,但這就沒有走正常的流程了。
final boolean transferAfterCancelledWait(Node node) {
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.
*/
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
首先通過CAS設置node狀態從CONDITION變成0,如果成功了,就將node入隊同步隊列,然後直接返回true。注意,這裏的CAS操作和transferForSignal
裏的compareAndSetWaitStatus(node, Node.CONDITION, 0)
是一樣的,也就是說:執行signal
的線程 和 因中斷被喚醒的node線程,在這句compareAndSetWaitStatus(node, Node.CONDITION, 0)
這裏有競爭關係,誰競爭贏了,誰纔有資格將node入隊同步隊列enq(node)
。
這樣的競爭關係是很必要的,直接避免兩個線程將同一個node重複入隊。
通過CAS設置node狀態從CONDITION變成0,如果失敗了,那就不能再執行enq(node)
啦。因爲肯定有另一個signal線程正在執行enq(node)
或者已經執行完了enq(node)
了。
但這裏我們如果發現另一個signal線程還沒有執行完了enq(node)
(通過!isOnSyncQueue(node)
條件判斷),就必須一直等待,直到另一個signal線程執行完了enq(node)
,然後循環纔可以退出。之所以這麼等一下,是因爲如果不等,node線程自己接下來就會執行到acquireQueued
了,而執行acquireQueued
的前提就是已經入隊同步隊列完畢。
等到node線程執行完了checkInterruptWhileWaiting
,考慮本文有中斷的場景,就會直接break出循環,然後執行到acquireQueued
。如果不能獲得鎖,還是會阻塞在acquireQueued
的shouldParkAfterFailedAcquire
裏。
因中斷被喚醒的node線程 和 signal線程 的競爭關係
上面說了中斷流程和signal流程誰在前面,await
的表現也會有所不同。具體的說,則體現在:因中斷被喚醒的node線程 和 signal線程 的競爭關係上。這兩個線程完全有可能同時在執行中,而它們的競爭點則體現在:
- 因中斷被喚醒的node線程。
transferAfterCancelledWait
函數裏的compareAndSetWaitStatus(node, Node.CONDITION, 0)
。 - signal線程。
transferForSignal
函數裏的compareAndSetWaitStatus(node, Node.CONDITION, 0)
。
這兩個transfer方法都會執行同一個CAS操作,但很明顯,只能有一個線程能夠執行CAS操作成功。
- 競爭成功的那一方,transfer方法會返回true,並且會執行
enq(node)
。 - 競爭失敗的那一方,transfer方法會返回false,並且不會執行
enq(node)
。 - 當一個處於條件隊列上的node,狀態從
CONDITION
變成0時,就意味着它正在前往同步隊列,或者已經放置在同步隊列上了。 - 如果
transferAfterCancelledWait
競爭成功,我們稱這個node線程走的是中斷流程。 - 如果
transferForSignal
競爭成功,我們稱這個node線程走的是signal流程。
終於執行到acquireQueued
講了半天,終於講到acquireQueued
了。但重點內容其實都在前面,acquireQueued
後面的都是一些善後處理而已了。
既然已經執行到了acquireQueued
,說明又會走獨佔鎖的獲取過程了,在此不贅述了。我們只需要知道,從acquireQueued
返回時,node線程已經獲取到了鎖,並且返回了acquireQueued
過程中是否有過中斷。注意,這和acquireQueued
執行前發生的中斷是兩個不同的中斷,acquireQueued
執行前發生的中斷會被checkInterruptWhileWaiting
消耗掉,並賦值給interruptMode
的。
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
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);
}
注意,int savedState = fullyRelease(node)
之前釋放的同步器狀態,現在acquireQueued(node, savedState)
都要重新全部再獲取回來。
acquireQueued
期間發生的中斷,重要性不如acquireQueued
之前發生的中斷。假設acquireQueued
發生了中斷,acquireQueued(node, savedState)
則返回true,然後此時interruptMode
有三種情況:
interruptMode
爲0,說明acquireQueued
之前沒發生過中斷。interruptMode != THROW_IE
判斷成功。所以需要將interruptMode
升級爲REINTERRUPT
。interruptMode
爲REINTERRUPT
,說明acquireQueued
之前發生過中斷(signal流程在中斷流程之前的那種)。interruptMode != THROW_IE
判斷成功。然後將interruptMode
從REINTERRUPT
變成REINTERRUPT
,這好像是脫褲子放屁,但邏輯這樣寫就簡潔了。interruptMode
爲THROW_IE
,說明acquireQueued
之前發生過中斷(中斷流程在signal流程之前的那種)。interruptMode != THROW_IE
判斷失敗。不會去執行interruptMode = REINTERRUPT
,因爲執行了反而使得中斷等級下降了。說到底,還是因爲acquireQueued
期間發生的中斷,重要性不如acquireQueued
之前發生的中斷。
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
如果發現node的條件隊列的nextWaiter
還沒有斷開,則需要做一下善後處理。回想signal流程和中斷流程:
- signal流程中(
signal
和signalAll
方法),都會執行first.nextWaiter = null;
的,所以如果node線程之前走的是signal流程,那這裏不會執行。 - 中斷流程中,不會去執行
first.nextWaiter = null;
的,所以如果node線程之前走的是中斷流程,那這裏會執行。
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt(); // 返回用戶代碼之前,自我中斷一下
}
最後根據interruptMode
來判斷做出不同的中斷反應。
await()總結
await()
對於使用者來說,進入await()
時是持有鎖的,阻塞後退出await()
時也是持有鎖的。signal() signalAll()
也是一樣。
從實現內部的持有鎖情況來看:
await()
在從開頭到fullyRelease
執行前,是持有鎖的。await()
在從fullyRelease
執行後 到acquireQueued
執行前,是沒有持有鎖的。await()
在acquireQueued
執行後到最後,是持有鎖的。signal() signalAll()
全程都是持有鎖的。
await()
的整體流程如下:
- 將當前線程包裝成一個node後(
Node node = addConditionWaiter()
),完全釋放鎖(int savedState = fullyRelease(node)
)。 - 當前線程阻塞在
LockSupport.park(this)
處,等待signal線程或者中斷線程的到來。 - 被喚醒後,到達
acquireQueued
之前,當前線程的node已經置於sync queue
之上了。 - 執行
acquireQueued
,進行阻塞式的搶鎖。 - 退出
acquireQueued
時,當前線程已經重新獲得了鎖,之後進行善後工作。
awaitUninterruptibly()
前面介紹的await
方法裏,中斷來臨時會使得當前線程離開while循環進而去執行acquireQueued
開始搶鎖。換句話說,await
方法允許,當前線程因爲中斷而不是因爲signal,而最終退出await
方法(畢竟acquireQueued
最終還是會搶鎖成功的)。
有時候我們希望,退出await
方法的原因,只能是因爲signal,所以就需要使用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();
}
首先看到awaitUninterruptibly
不會拋出中斷異常,我們拿它與await
方法進行下對比:
public final void await() throws InterruptedException {
if (Thread.interrupted()) // 不同之處1
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0; // 不同之處2
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) // 不同之處3
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE) // 不同之處4
interruptMode = REINTERRUPT;
// 後面的善後工作全都不需要做了
if (node.nextWaiter != null) // 不同之處5
unlinkCancelledWaiters();
if (interruptMode != 0) // 不同之處6
reportInterruptAfterWait(interruptMode);
}
可以發現awaitUninterruptibly
方法只是對await
方法進行了大面積的刪改:
- 函數開頭不需要檢查調用前是否被中斷過了
- 用來記錄中斷狀態的變量只需要記錄兩種狀態(中斷過,或沒有中斷),所以用
boolean
變量就夠了。 - 在while循環裏,當前線程如果只是因爲中斷而被喚醒,那麼消耗掉中斷狀態(
Thread.interrupted()
)。如果還沒有signal線程的到來,那麼當前線程的node還是不處於sync queue
之上的,所以下次循環繼續,然後又阻塞在LockSupport.park(this)
這裏。 - 如果搶鎖過程中(
acquireQueued(node, savedState)
)發生過中斷,或者搶鎖之前發生過中斷(|| interrupted
),那麼就自我中斷一下。 - 不需要判斷當前線程node在條件隊列上的鏈接是否斷開,因爲
awaitUninterruptibly
方法只會因爲signal流程會退出while循環,而signal流程肯定會 斷開條件隊列上的鏈接的。 - 不需要執行
reportInterruptAfterWait
了,因爲自我中斷已經做過了。
經過上面分析可以發現,awaitUninterruptibly
方法全程都不會響應中斷,不管是在搶鎖過程之前還是之中發生過中斷,都是隻是簡單地自我中斷一下就好了。
而因爲awaitUninterruptibly
方法不會去執行checkInterruptWhileWaiting
,所以要想滿足退出while循環的條件!isOnSyncQueue(node)
進而去執行acquireQueued
開始搶鎖,只能是因爲signal流程中執行了transferForSignal
(裏面執行了enq(node)
,使得node入隊了sync queue
)。而從使用者的角度看,中斷並不能使得線程從await
調用處喚醒,只有執行了signal,線程才能從await
調用處喚醒。
總結一下awaitUninterruptibly
方法:
- 中斷會喚醒當前線程,但當前線程的node還是不處於
sync queue
之上,所以當前線程馬上又會阻塞。 - 只有signal方法纔可以使得當前線程的node處於
sync queue
之上。 - 調用該方法中,如果發生了中斷,會在返回用戶代碼之前,自我中斷一下。
awaitNanos(long nanosTimeout)
前面的方法不管是await
和awaitUninterruptibly
,它們在while循環中如果一直沒有中斷線程或者signal線程的到來,會一直阻塞在while循環的park
處。如果長時間signal線程一直不來,當前線程就會一直阻塞(一直阻塞就會一直不會去執行acquireQueued
,也就不可能執行完函數了),所以此時我們可能需要一個帶有超時機制的awaitNanos(long nanosTimeout)
,如果超時了就啥也不用管,直接去執行acquireQueued
。
參數nanosTimeout
,代表你最多願意在這個方法等待多長時間。
返回值long
,代表nanostimeout
值 減去 花費在等待在此方法上的時間 的估算。
public final long awaitNanos(long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
final long deadline = System.nanoTime() + nanosTimeout; // 不同之處1
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
if (nanosTimeout <= 0L) { // 不同之處2
transferAfterCancelledWait(node);
break;
}
if (nanosTimeout >= spinForTimeoutThreshold) // 不同之處3
LockSupport.parkNanos(this, nanosTimeout);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
nanosTimeout = deadline - System.nanoTime(); // 不同之處4
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return deadline - System.nanoTime(); // 不同之處5
}
讓我們直接來看awaitNanos(long nanosTimeout)
方法它與await()
的不同之處:
- 需要計算出一個
deadline
,作爲是否超時的標準。 - 如果
LockSupport.parkNanos(this, nanosTimeout)
之後的這段長達nanosTimeout的時間段內,既沒有中斷來臨(不會進入if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
分支而break出循環),也沒有signal來臨(那麼當前線程node還是不處於sync queue
上,循環條件!isOnSyncQueue(node)
通過),線程足足阻塞了nanosTimeout這麼久才被喚醒,那麼經過nanosTimeout = deadline - System.nanoTime()
重新計算後,就肯定會進入到if (nanosTimeout <= 0L)
分支,執行transferAfterCancelledWait
將node入隊同步隊列,然後退出循環,開開心心地去執行acquireQueued
就好了。 - 只有當
nanosTimeout >= spinForTimeoutThreshold
,纔可以阻塞當前線程,不然時間太短的話,就直接自旋就好了。這是因爲考慮到阻塞線程和喚醒線程的過程,時間太短就不好控制了。注意進入這個分支的可能性:- 用戶給的
nanosTimeout
太小,第一次進入循環時,就開始自旋。 - 走了signal流程,signal流程在一定條件下喚醒了當前線程。但喚醒時剩餘時間已經很少了。
- 走了signal流程,但沒有喚醒當前線程。之後當前線程node已經成爲了head後繼,然後另一個線程執行了
unlock
,喚醒了當前線程。但喚醒時剩餘時間已經很少了。 - 不可能是走的中斷流程。因爲會直接break出循環,也就不會執行這個分支。
- 用戶給的
nanosTimeout = deadline - System.nanoTime()
計算出剩餘時間還有多久。能執行到這裏,說明之前肯定沒有過中斷。return deadline - System.nanoTime()
返回剩餘時間還有多久。
我們來總結下awaitNanos(long nanosTimeout)
中能執行到acquireQueued
的幾種流程:
- 都已經超時了,且之前沒有過中斷,那麼接下來就會去執行
acquireQueued
,不過分兩種情況:- 如果之前signal線程來過,signal線程就已經把當前線程node放到同步隊列裏去了,所以
!isOnSyncQueue(node)
循環條件不成立,直接退出循環進而去執行的acquireQueued
。 - 如果之前signal線程沒有來過,
!isOnSyncQueue(node)
循環條件成立,進入if (nanosTimeout <= 0L)
分支去執行transferAfterCancelledWait
讓當前線程node先入隊後,再去執行acquireQueued
。
- 如果之前signal線程來過,signal線程就已經把當前線程node放到同步隊列裏去了,所以
- 因爲來了中斷,而去執行的
acquireQueued
。這個過程和await()
一樣。 - 因爲signal流程中的一定條件的喚醒,或因爲執行
unlock
的線程而喚醒。這兩種情況,當前線程node都已經處於同步隊列上了,所以循環條件不成立而退出循環,進而去執行的acquireQueued
。
if (nanosTimeout <= 0L) {
transferAfterCancelledWait(node);
break;
}
當然,如果用戶給的參數nanosTimeout
本來就是<=0
的,第一次循環就會直接將當前線程node加入到同步隊列中,然後退出循環後進而執行acquireQueued
。
總結一下awaitNanos(long nanosTimeout)
:相比await()
方法,它能在超時後,無條件地去執行acquireQueued
,而這不需要signal線程或中斷線程的到來。
await(long time, TimeUnit unit)
理解了awaitNanos(long nanosTimeout)
,這個await(long time, TimeUnit unit)
方法就好懂多了。從參數上就可以看出來,它只是對時間單位進行了拓展。我們直接看看它與awaitNanos(long nanosTimeout)
的不同之處。
public final boolean await(long time, TimeUnit unit) //不同之處1
throws InterruptedException {
long nanosTimeout = unit.toNanos(time); //不同之處2
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
final long deadline = System.nanoTime() + nanosTimeout;
boolean timedout = false; //不同之處3
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
if (nanosTimeout <= 0L) {
timedout = transferAfterCancelledWait(node); //不同之處4
break;
}
if (nanosTimeout >= spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
nanosTimeout = deadline - System.nanoTime();
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return !timedout; //不同之處5
}
來分析一下不同之處:
- 返回值是
boolean
型。 - 將時間最終都轉換爲nanos的時間。
- 增加了一個局部變量
timedout
,代表退出while循環是否是因爲超時才退出的。退出循環後會接着執行acquireQueued
。 - 如果當前線程因爲超時而喚醒,會進入到
if (nanosTimeout <= 0L)
分支,之後如果transferAfterCancelledWait
執行成功,會將局部變量timedout
置爲true,代表退出while循環是因爲超時才退出的。 - 返回
!timedout
,所以返回值爲false,代表退出while循環是因爲超時才退出的;返回值爲true,代表退出while循環不是因爲超時才退出的。
可以發現,await(long time, TimeUnit unit)
的整個過程與awaitNanos(long nanosTimeout)
幾乎一樣,只是返回值類型不一樣了。但await(long time, TimeUnit unit)
只關心退出while循環的原因,awaitNanos(long nanosTimeout)
關心的是整個執行過程中花費的時間。
所以,並不能簡單的認爲,調用await(long time, TimeUnit unit)
等價於 調用awaitNanos(unit.toNanos(time)) > 0
。比如考慮這種場景,假設沒有中斷,退出循環是因爲signal線程來過才退出的循環,但直到執行unlock
的線程來喚醒當前線程進而使得當前線程得到鎖卻很遲。此時:
awaitNanos(unit.toNanos(time))
返回的值是小於0的,所以awaitNanos(unit.toNanos(time)) > 0
返回false。await(long time, TimeUnit unit)
退出while循環之前,不會去執行timedout = transferAfterCancelledWait(node)
,因爲是直接不滿足了循環條件!isOnSyncQueue(node)
。所以timedout = false
,返回!timedout
,即返回的是true了。- 按照上面兩點,發現二者不一致了。
awaitUntil(Date deadline)
這個方法其實和await(long time, TimeUnit unit)
幾乎一模一樣,只是獲得deadline的方式改變了而已,以前是自己計算出來。
public final boolean awaitUntil(Date deadline)
throws InterruptedException {
long abstime = deadline.getTime(); //不同之處1
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
// 此處不需要計算出deadline了,因爲參數給了。 不同之處2
boolean timedout = false;
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
if (System.currentTimeMillis() > abstime) { //不同之處3
timedout = transferAfterCancelledWait(node);
break;
}
LockSupport.parkUntil(this, abstime); //不同之處4
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return !timedout;
}
分析一下不同之處:
long abstime = deadline.getTime()
,直接獲得這個絕對時間。- 此處不需要計算出deadline了,因爲參數給了。
- 以前是判斷
deadline
與當前系統時間 之間的差值。現在是比較deadline
與當前系統時間 之間的大小。 - 相比之前兩個超時版本,這裏沒有使用自旋優化,在剩餘時間特別短的時候。所以調用這個方法時,最好給定的絕對時間比較遠,才比較好。