在看本文前,如果對ReentrantLock不瞭解的,請先看 ReentrantLock源碼分析,以免有些地方無法理解。
代碼的執行說明,都已註釋的方式寫在了代碼中,如果想看某個方法的分析,直接搜索方法名即可。當然,本文中對於在ReentrantLock中詳細說明過的方法以及相關分析(如將節點加入到同步隊列的方法 enq,將節點掛起的方法acquireQueued,waitstatus狀態說明等),在這裏沒有詳細說明,請直接看ReentrantLock源碼分析
Condition.await
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//將當前線程封裝爲Node節點加入到wait隊列中
Node node = addConditionWaiter();
//在wait將項城掛起前釋放鎖
//在釋放鎖的同時,會那單線程的重入次數State。後面再講線程重新掛起到lock上的時候,會還原原有的state
int savedState = fullyRelease(node);
int interruptMode = 0;
//判斷當前節點是否是已經在同步隊列上了,如果已經在同步隊列上了,就不需要掛起。
//當然,這裏將線程掛起後,當線程被single喚醒,將通過這個判斷跳出循環,因爲在single將線程喚醒後會同時將線程Node加入到同步隊列中。
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
//如果Node節點是中斷喚醒的,則通過這裏跳出循環
//這裏需要注意的一個點是給interruptMode賦值了,interruptMode 記錄了不同的中斷方式(拋異常還是interupt)
//通過後面的分析可以看到,如果Node是因爲中斷導致將Node加入到Lock,則將中斷方式設置爲拋異常。
//也就是線程還在wait隊列中時候被中斷了,則對外的終端是拋異常告知。如果是在lock中被中斷,則告知的方式是通過interupt方式。
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//執行到這裏,說明被wait的線程已經喚醒了,並且已經將線程加入到了同步隊列中。
//這裏首先通過acquireQueued將加入到同步隊列中的Node節點掛起
//一定要注意:一旦線程在這裏通過acquireQueued掛起,線程將不會再繼續往下執行,只有當線獲取鎖之後,纔會從這裏繼續往下執行。
//並且這裏掛起的時候,會還原線程原本的重入次數savedState 。
//如果一旦獲取了鎖,會再次判斷是否是拋異常,如果不是,設置中斷方式就是interupt的方式
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
//這裏判斷Node節點的nextWaiter 是否等於空。
//通過這個判斷當前節點是否還繼續鏈接在wait隊列上,如果還繼續鏈接在wait隊列上,就將其從wait隊列上刪除。
//通過後面對addConditionWaiter的分析,你會看到,wait是單向隊列,節點之間是通過addConditionWaiter連接的。
//通過後面分析,你會發現,如果是中斷將節點加入到了同步隊列,並不會將wait隊列上的節點刪掉。
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
//這裏根據不同中斷方式,對外進行不同的中斷相應
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
//這個方法的作用,就是將當前線程封裝成爲Node節點並加入到wait隊列。
//wait是一個單向隊列,firstWaiter 指向頭結點,lastWaiter指向尾巴節點,節點之間通過nextWaiter鏈接,並且處於wait隊列上的正常的節點的waitStatus 狀態都爲CONDITION(如果被喚醒並加入到同步隊列,就會將其改爲0)
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
//這裏判斷尾節點的waitStatus是否是CONDITION,如果不是,則通過unlinkCancelledWaiters刪除那些不符合條件的節點。
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//從wait隊列上刪除不符合條件的節點後,就將當前線程節點鏈接到wait隊列上。
//這裏不用考慮安全問題,因爲既然要調用wait必須要先獲得鎖。
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
//將隊列上,waitStatus 不等於CONDITION的節點去掉。
//這個邏輯很簡單,就是簡單的鏈表操作,就不逐句分析了.
//遍歷鏈表,維護了三個連續指針,中間指針一旦waitStatus 不等於CONDITION,就將其從節點上去掉。
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
//釋放鎖,這裏之所以使用fullyRelease,意思就是如果一個鎖多次重入,那麼就需要一次性將多次重入一同釋放。
//這對於ReentrantLock來說當前是很簡單的,只需要將state設置成0即可。
final int fullyRelease(Node node) {
boolean failed = true;
try {
//獲取當前線程持有的state。state也代表了鎖的重入次數
int savedState = getState();
//將原state作爲參數,一次性全部釋放掉。釋放成功了,返回wait前持有的鎖的個數
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
//判斷當前節點是否已經存在於同步隊列上了
final boolean isOnSyncQueue(Node node) {
//如果已經放入了同步隊列。waitStatus一定不爲CONDITION,並且他的prev一定不爲NULL。
//所以滿足下面的任何一個條件,說明都沒有放到同步隊列上。所以返回false。
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//如果不爲null,說明一定在同步隊列上了,返回true。
if (node.next != null) // If has successor, it must be on queue
return true;
/*
* node.prev can be non-null, but not yet on queue because
* the CAS to place it on queue can fail. So we have to
* traverse from tail to make sure it actually made it. It
* will always be near the tail in calls to this method, and
* unless the CAS failed (which is unlikely), it will be
* there, so we hardly ever traverse much.
*/
//前面的兩個if如果滿足條件就立即返回了,如果都不滿足,就只有遍歷同步隊列來逐個檢查了,當然比較浪費時間。
return findNodeFromTail(node);
}
//遍歷lock逐個比較
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
//判斷是否中斷,如果線程沒有中斷,則返回0。如果中斷了,則通過transferAfterCancelledWait判斷是否是放入lock前發生的中斷。
//如果是,則通過拋異常響應中斷,如果不是,則通過interupt的方式響應中斷
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
//判斷是否是放入lock前發生的中斷
final boolean transferAfterCancelledWait(Node node) {
//如果當前節點的waitstatus爲Node.CONDITION,則當前節點就一定還在wait隊列上。因爲已經發生了中斷,所以就需要將其放入到同步隊列。
//放入之前,先通過CAS將waitstatus替換爲0。如果替換失敗了,說明其他線程正在將當前節點放入到lock。
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
//如果替換成功了,則通過enq將節點放入到同步隊列
enq(node);
//說明是在wait中發生的中斷,則返回true
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替換失敗,說明有其他線程正在將Node放入到同步隊列中,但是不已經已經放入成功了,可能只是修改了waitstatus,還沒有最終放入,所以這裏如果判斷還沒有完全放入成功的時候,先通過死循環加yield自旋等待一會。
while (!isOnSyncQueue(node))
Thread.yield();
//知道到這,說明是其他線程已經將節點放入了同步隊列,說明不是在wait隊列中發生的中斷。
return false;
}
//通過不同的中斷方式,對外進行中斷的響應
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
Condition.signal
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
//如果wait隊列有被wait的節點,則進行喚醒第一個節點
if (first != null)
doSignal(first);
}
//喚醒wait隊列上的第一個節點
private void doSignal(Node first) {
do {
//判斷是否隊列上只有一個節點,如果是,就將lastWaiter 設置爲null
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
//既然要喚醒第一個節點,喚醒後,直接將第一個節點從隊列上移除。
first.nextWaiter = null;
//通過transferForSignal將節點加入到同步隊列中。如果加入失敗了,則重新取出wait隊列的第一個節點並喚醒。
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
//將wait節點放入到同步隊列中
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
//這裏如果CAS失敗,說明當前節點的狀態已經不是CONDITION,說明已經被其他線程喚醒了。
//返回false之後,外層是一個循環,會繼續取出wait隊列的第一個節點,繼續執行喚醒操作
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
//將當前節點加入到同步隊列,注意,這裏enq返回值返回的是假如隊列節點的前一個節點,也就是原隊尾節點。
Node p = enq(node);
int ws = p.waitStatus;
//這裏的判斷非常巧妙,我們知道,我們把Node節點從wait移動到同步隊列,如果我們把節點線程喚醒,是沒有問題的。
//因爲喚醒後執行到await中的acquireQueued的時候,會被重新掛起,但是這樣比較耗費性能,是沒有必要的。
//所以這裏進行了判斷,如果移入到同步隊列後,發現原尾節點的狀態大於0,或者將尾節點的狀態改爲SIGNAL的時候失敗了,纔會喚醒。並在acquireQueued中重新整理同步隊列並重新掛起。
//這裏不掛起是沒有問題的,因爲在acquireQueued掛起前判斷,如果當前節點是第一個節點,會直接獲取鎖。如果中斷喚醒了,或繼續從await掛起的地方繼續執行,會繼續在acquireQueued的地方重新掛起。
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
Condition.signalAll
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//拿到wait隊列,執行doSignalAll
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
//理解了signal,理解這裏的doSignalAll就很簡單了。
//就是遍歷wait隊列上的節點逐個順序取出放入到同步隊列中。
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}