Java併發包下的鎖(4)——Condition介面
Condition 介面提供了類似Object的監視器方法,與Lock配合可以實現 等待/通知 模式
Condition的介面
在說Condition的介面之前,先對比一下與Object監視器的異同:
對比項 | Object的監視器(Monitor) | Condition |
---|---|---|
前置條件 | 獲取物件的鎖 | 呼叫Lock.lock()獲取鎖呼叫Lock.newCondition獲取Condition物件 |
呼叫方式 | 直接呼叫:object.wait() | 直接呼叫:condition.await() |
等待佇列個數 | 一個 | 多個 |
當前執行緒釋放鎖並進入等待狀態 | 支援 | 支援 |
當前執行緒釋放鎖並進入等待狀態,在等待狀態中不響應中斷 | 不支援 | 支援 |
當前執行緒釋放鎖並進入超時等待狀態 | 支援 | 支援 |
當前執行緒釋放鎖並進入超時等待狀態到將來某一個時間 | 不支援 | 支援 |
喚醒等待佇列中的一個執行緒 | 支援 | 支援 |
喚醒等待佇列中的全部執行緒 | 支援 | 支援 |
Condition介面的實現在兩個佇列同步器:AbstractQueuedSynchronizer和AbstractQueuedLongSynchronizer中,它們通過一個內部類ConditionObject聚合了其中的操作。下面來看看Condition提供的API有哪些:
// 呼叫該方法執行緒會釋放鎖並進入等待狀態。呼叫該方法的執行緒要想進入執行狀態的情況有:
// 其他執行緒呼叫該Condition的signal()或signalAll()方法,當前執行緒被選中喚醒
// 1. 其他執行緒中斷當前執行緒
// 2. 如果當前等待執行緒從await()方法返回,表名該執行緒已經獲取了Condition物件對應的鎖
void await() throws InterruptedException;
// 對中斷不敏感
void awaitUninterruptibly();
// 當前執行緒進入等待狀態,直到被通知、中斷或者超時才返回。返回值表示剩餘的時間
long awaitNanos(long nanosTimeout) throws InterruptedException;
// 當前執行緒進入等待狀態,直到被通知、中斷或者到某一個時間。到了指定時間返回true,否則返回false
boolean awaitUntil(Date deadline) throws InterruptedException;
// 喚醒一個等待在Condition上的執行緒,該執行緒從等待方法返回前必須獲得與Condition相關聯的鎖
void signal();
// 喚醒所有等待在Condition上的執行緒,能夠從等待方法返回的執行緒必須與Condition相關聯的鎖
void signalAll();
下面的示例展示了Condition的基本用法:
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException {
lock.lock();
try {
condition.await()
} finally {
lock.unlock();
}
}
public void conditionSignal() throws InterruptedException {
lock.lock();
try {
condition.signal()
} finally {
lock.unlock();
}
}
Condition的實現
ConditionObject 是同步器 AQS 的內部類,每個 Condition 物件都包含著一個佇列,稱為等待佇列,該佇列是 Condition 物件實現等待/通知功能的關鍵。
1. 等待佇列
等待佇列是一個FIFO的佇列,在佇列中每個節點都包含了一個執行緒引用,該執行緒就是在 Condition 物件上等待的執行緒,如果一個執行緒呼叫了 Condition.await() 方法,那麼該執行緒將會釋放鎖、構造成節點並加入等待佇列進入等待狀態。一個Condition包含一個等待佇列,Condition擁有首節點(firstWaiter)和尾節點(lastWaiter),當前執行緒呼叫 Condition.await() 方法,將會以當前執行緒構造節點,並將節點從尾部加入等待佇列。
在節點的更新過程中,並不需要CAS的保證,因為呼叫await()方法的執行緒必定是已經獲取了鎖的執行緒,不會出現執行緒安全問題,該過程是由鎖的機制來保證的。
在Object的監視器模型上,一個物件擁有一個同步佇列和等待佇列,而併發包中的Lock(同步器)擁有一個同步佇列和多個等待佇列,如下圖:
2. 等待
當呼叫 await() 方法時,相當於同步佇列的首節點釋放了同步狀態,從同步佇列中移出,並構造成新的節點移動到Condition的等待佇列中
下面是 await() 方法的原始碼:
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);
}
下圖是當前執行緒釋放同步狀態加入等待佇列的過程:
綜合上面所講,總結一下await()幹了些什麼事兒:
-
當前執行緒為獲取了鎖的執行緒(同步佇列中的首節點)
-
將當前執行緒構造成新的節點(等待節點),並加入等待佇列
-
釋放該執行緒的同步狀態,喚醒同步對列中的後繼節點,然後該執行緒在等待佇列中等待
3. 通知
呼叫Condition的signal()方法,將喚醒在等待佇列中等待時間最長的節點(首節點),在喚醒節點之前,會將節點移到同步佇列中。signal() 的原始碼如下:
public final void signal() {
// 如果當前執行緒沒有獲取到鎖則丟擲異常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 獲取等待佇列中的首節點
Node first = firstWaiter;
// 如果首節點不為空,將其移到同步佇列,並呼叫LockSupport喚醒節點中的執行緒
if (first != null)
doSignal(first);
}
節點從等待佇列移動到同步佇列的過程如下:
成功獲取同步狀態後,被喚醒的執行緒將從先前呼叫的await()方法返回,此時該執行緒已經成功獲取了鎖。Condition的signalAll()方法,相當於對等待佇列中的每一個節點均執行一次signal()方法,其結果就是將等待佇列中的所有節點全部移入到同步佇列中,並喚醒每個節點的執行緒。
參考
《Java併發程式設計的藝術》