Table of Contents
作用
Condition 是 jdk1.5 引入的用於代替監視器方法,比如 wait,notify,notifyAll,提供更加易用和靈活的同步阻塞方法。
Condition 翻譯暫時翻譯爲競態條件,用作是在其他線程通知喚醒之前阻塞一個線程。在多線程環境下如果出現資源競爭問題,需要保證多線程操作同一資源時,需要順序執行,以免出現線程安全問題。
線程在使用競態條件時的阻塞效果和 wait()方法類似,但是本質上靜態條件是一種鎖。等待滿足一種靜態條件其實是通過靜態條件的 newCondition()方法初始化初始化一個特殊的鎖。
一下是一個競態條件的例子:
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
上面是一個包含 put(存放一個元素)和 take(取走一個元素)並且由有邊界的容器的例子。如果容器已經達到容量上限,那麼調用 put 方法的線程會被阻塞。如果有其他線程成功取走元素時,調用 put 被阻塞的線程會被喚醒。
競態條件比監視器的阻塞和喚醒增加了一些功能,比如可以指定喚醒的順序和通知時不需要持有鎖。
競態條件的實例也是一個普通的 Java 對象,具備 Object.wait 方法和 notify 方法,並且也可以單獨使用這些監視器方法。但是由於這些監視器方法和靜態條件本身設計的方法是相互獨立的,所以除非是實現類有特殊的設計,否則建議不要使用靜態條件的監視器方法,以免混淆。
由於平臺本身可能會發生虛假喚醒(Spurious Wakeup),所以在設計等待條件時,需要放在一個循環體中判斷。如果放在 if 條件判斷中語句中,此時發生虛假喚醒,那麼條件不通過,程序將不按照預定的設計走。
競態條件的等待方法(interruptible,non-interruptible,timed)在各個平臺上的實現和性能上的表現可能會不一致。而且在某些平臺上很難提供按順序喚醒的特性,更有甚者線程中斷的功能也不是在所有平臺上都能夠讓線程立即暫停的。
通常情況下不需要競態條件的實現類實現順序喚醒的功能,也沒有必要讓線程立即終止。但是要求實現類明確指出這些功能確切的表現形式,以免發生誤解。中斷操作通常意味着終止一個線程。但是如果中斷操作發生在線程正在處理一個任務的過程中,那麼線程無法被記錄阻塞。實現類應該把這種行爲描述清楚。
接口定義
/**
* Causes the current thread to wait until it is signalled or
* {@linkplain Thread#interrupt interrupted}.
*
* 在得到通知喚醒或者線程中斷之前一組阻塞等待。
*
* <p>The lock associated with this {@code Condition} is atomically
* released and the current thread becomes disabled for thread scheduling
* purposes and lies dormant until <em>one</em> of four things happens:
* <ul>
* <li>Some other thread invokes the {@link #signal} method for this
* {@code Condition} and the current thread happens to be chosen as the
* thread to be awakened; or
* <li>Some other thread invokes the {@link #signalAll} method for this
* {@code Condition}; or
* <li>Some other thread {@linkplain Thread#interrupt interrupts} the
* current thread, and interruption of thread suspension is supported; or
* <li>A "<em>spurious wakeup</em>" occurs.
* </ul>
*
* 如果有以下的一種情況發生:1)某個線程喚醒當前線程(Condition.signal),並且當前線程被選擇喚醒;
* 2)某個線程喚醒所有等待的線程(Condition.signalAll);
* 3)發生了一次虛假喚醒(spurious wakeup);
*
* <p>In all cases, before this method can return the current thread must
* re-acquire the lock associated with this condition. When the
* thread returns it is <em>guaranteed</em> to hold this lock.
*
* 如果線程由於調用阻塞方法被阻塞了,希望在被喚醒之後執行,首先必須要保證線程能夠獲得競態條件關聯的鎖。
* 正如 BoundedBuffer 中的例子,在 put 時,先獲得鎖。如果隊列已經達到容量上限,那麼調用 notFull.await()。
* notFull.await()方法會讓當前線程阻塞並原子釋放對象鎖。
*
* <p>If the current thread:
* <ul>
* <li>has its interrupted status set on entry to this method; or
* <li>is {@linkplain Thread#interrupt interrupted} while waiting
* and interruption of thread suspension is supported,
* </ul>
* then {@link InterruptedException} is thrown and the current thread's
* interrupted status is cleared. It is not specified, in the first
* case, whether or not the test for interruption occurs before the lock
* is released.
*
* 如果線程在等待(waiting)或阻塞(Blocked)狀態(Bloked)時,線程被設置中斷狀態爲被中斷(interrupted),
* 那麼會拋出 InterruptedException 異常,並且被中斷(interrupted)標記被清除。
* 第一次發生中斷操作之後,線程持有的鎖也會被原子釋放。
*
* <p><b>Implementation Considerations</b>
*
* <p>The current thread is assumed to hold the lock associated with this
* {@code Condition} when this method is called.
* It is up to the implementation to determine if this is
* the case and if not, how to respond. Typically, an exception will be
* thrown (such as {@link IllegalMonitorStateException}) and the
* implementation must document that fact.
*
* <p>An implementation can favor responding to an interrupt over normal
* method return in response to a signal. In that case the implementation
* must ensure that the signal is redirected to another waiting thread, if
* there is one.
*
* 注意事項:
* 線程持有競態條件相關鎖的情況下才調用競態條件的方法。
* 如果未持有鎖的情況下調用競態條件的方法,那麼可以拋出一個 IllegalMonitorStateException 異常。並且接口文檔上需要說明。
* 如果實現類通過中斷來喚醒其他線程,那麼請確保 signal 也能喚醒其他線程。
*
* @throws InterruptedException if the current thread is interrupted
* (and interruption of thread suspension is supported)
*/
void await() throws InterruptedException;
/**
* Causes the current thread to wait until it is signalled.
*
* 線程在被喚醒之前一直等待。正如方法名稱所示,該方法忽略線程中斷條件校驗。
*
*/
void awaitUninterruptibly();
/**
* Causes the current thread to wait until it is signalled or interrupted,
* or the specified waiting time elapses.
*
* 在線程被中斷或者被喚醒或者超過等待時間前線程一直處於等待狀態
*
*
* @param nanosTimeout the maximum time to wait, in nanoseconds
* @return an estimate of the {@code nanosTimeout} value minus
* the time spent waiting upon return from this method.
* A positive value may be used as the argument to a
* subsequent call to this method to finish waiting out
* the desired time. A value less than or equal to zero
* indicates that no time remains.
* @throws InterruptedException if the current thread is interrupted
* (and interruption of thread suspension is supported)
*/
long awaitNanos(long nanosTimeout) throws InterruptedException;
/**
* Causes the current thread to wait until it is signalled or interrupted,
* or the specified waiting time elapses. This method is behaviorally
* equivalent to:
*
* 同 awaitNanos 方法。
* <pre> {@code awaitNanos(unit.toNanos(time)) > 0}</pre>
*
* @param time the maximum time to wait
* @param unit the time unit of the {@code time} argument
* @return {@code false} if the waiting time detectably elapsed
* before return from the method, else {@code true}
* @throws InterruptedException if the current thread is interrupted
* (and interruption of thread suspension is supported)
*/
boolean await(long time, TimeUnit unit) throws InterruptedException;
/*
* 通 awaitNanos 方法。awaitNanos 是給定距離現在的納秒,awaitUntil 是給定截止日期。
*/
boolean awaitUntil(Date deadline) throws InterruptedException;
/**
* Wakes up one waiting thread.
*
* 喚醒其他等待線程
*
* <p>If any threads are waiting on this condition then one
* is selected for waking up. That thread must then re-acquire the
* lock before returning from {@code await}.
*
* 被喚醒的線程在調用 await 方法前必須持有鎖。
*
* <p><b>Implementation Considerations</b>
*
* <p>An implementation may (and typically does) require that the
* current thread hold the lock associated with this {@code
* Condition} when this method is called. Implementations must
* document this precondition and any actions taken if the lock is
* not held. Typically, an exception such as {@link
* IllegalMonitorStateException} will be thrown.
*
* 注意事項:
* 實現類在調用 signal 方法前必須先持有鎖。明確明確說明如果未持有鎖但是卻調用了 signal 方法,
* 會拋出 IllegalMonitorStateException 異常
*/
void signal();
/**
* Wakes up all waiting threads.
*
* 喚醒所有的等待線程。
* 其他注意事項通 signal 類似。
*
* <p>If any threads are waiting on this condition then they are
* all woken up. Each thread must re-acquire the lock before it can
* return from {@code await}.
*
* <p><b>Implementation Considerations</b>
*
* <p>An implementation may (and typically does) require that the
* current thread hold the lock associated with this {@code
* Condition} when this method is called. Implementations must
* document this precondition and any actions taken if the lock is
* not held. Typically, an exception such as {@link
* IllegalMonitorStateException} will be thrown.
*/
void signalAll();
ConditionObject 實現類
AbstractQueuedSynchronizer
一個基於先進先出的等待隊列實現的在多線程環境下,提供線程阻塞和取消阻塞的同步器。
Node
AbstractQueuedSynchronizer.ConditionObject 依賴 AbstractQueuedSynchronizer.Node。在介紹 ConditionObject 只來,先來看一下 Node 的實現。
Node 是表示等待隊列的元素,本身有上一個節點的指向和下一個節點的指向,多個節點構成一個鏈表特性的等待隊列。
等待隊列是 CLH(Craig,Landin,and Hagersten)鎖隊列的一種變種。CLH 鎖是一隊列(本身由一組節點組成的鏈表)鎖,能夠確保無飢餓和先來先得的公平性,一般用做自旋鎖。
AbstractQueuedSynchronizer 中,由 Node 組成的隊列鎖並非用作自旋鎖,而是做成了同步機制。實現同步機制的策略和自旋鎖的策略類似,也是通過控制前一個節點的信息來實現。節點中(Node)有個屬性“status”跟蹤線程的阻塞狀態,會隨着線程的阻塞狀態變更而變更。如果當前節點的線程已經執行完畢或則已經可以釋放共享資源時,會將節點釋放,即鏈路第一個節點出隊列。鏈路第一個節點出隊列時,會通知後續的節點。後續節點在得到其對應的節點通知前,一直在試圖獲取是否可以往下執行的校驗(循環校驗是否滿足往下執行的條件,而並非是編程阻塞狀態)。如此可達到讓線程按照順序自旋阻塞和按照順序先後執行。
由於線程在獲取節點時可能和其他線程一起競爭同一順序的節點,可能會導致獲取失敗繼續等待。
將一個元素添加到 CLH 隊列中只需要對隊尾元素做修改,將隊尾元素的下一個元素指向到新元素中,並將隊尾的引用指向新增加的元素(具體的操作會比這複雜)。這個操作需要保證原子操作。同樣的出隊列也只需要操作隊列第一個元素,將隊列的第一個元素指向到之前第一個元素的下一個元素。該操作也要保證是原子性。並且由於等待時間過長或者線程中斷等因素,還需要考慮操作是否真正成功。
Node 設計的有許多種狀態,比如 CANCELLED(取消狀態,表示節點關聯的線程被終止了),SIGNAL(通知,表示競爭到節點的線程被允許執行了),CONDITION(等待,表示線程在等待合適的時機執行),PROPAGATE(傳播,在共享模式中(多個線程共享一個節點)中,下一個貢獻節點可以等待轉播)。如果節點被設置成 CANCELLED 狀態,那麼上一個節點引用指向該節點的需要重新指向一個未被職位取消置爲 CANCELLED 的節點。
ConditionObject
ConditionObject 是一個單向列表的條件隊列。結合 Node 實現的單向鏈表,爲 AbstractQueuedSynchronizer 提供阻塞(await,awaitNanos,awaitUntil 等)和取消阻塞(doSignal,doSignalAll)功能。
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
/**
* 隊列的的第一個 Node 元素。通知方法(signal 和 signalAll)一般都只針對隊列的第一個元素的。
* 通知方法會告通知下一個元素。
*
*/
private transient Node firstWaiter;
/**
* 隊列的最後一個元素。入隊操作(addConditionWaiter)需要知道在操作前隊列最後一個元素的位置,
* 並添加一個等待元素到隊尾,並將隊尾引用指向新增加的元素。
*/
private transient Node lastWaiter;
/**
* Creates a new {@code ConditionObject} instance.
*/
public ConditionObject() { }
// Internal methods
/**
* 添加一個元素到隊尾。此時新增加的元素關聯的線程是當前線程。
* @return its new wait node
*/
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.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;
}
/**
* 將 Node 鏈表(ConditionObject 對象持有的 Node 鏈表)中指定的元素(一般是鏈表中第一個元素)
* 依次將 Node 的狀態從 CONDITION(-2)變更成 0,
* 即 Node 從等待條件狀態變成條件已經滿足的狀態,
* 並且轉移到同步隊列中(AbstractQueuedSynchronizer 持有的 Node 鏈表)。
* @param first (non-null) the first node on condition queue
*/
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
/**
* 將 ConditionObject 對象持有的 Node 鏈表上所有的元素都移到 AbstractQueuedSynchronizer 對象
* 持有的 Node 元素鏈表上。
* @param first (non-null) the first node on condition queue
*/
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}
/**
* 如果鏈表中的元素的狀態不是等待條件的(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;
}
}
// public methods
/**
* 將條件隊列中的第一個元素移到 將 ConditionObject 對象持有的 Node 鏈表的第一個元素移到
* AbstractQueuedSynchronizer 對象持有的 Node 鏈表上
*
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
* returns {@code false}
*/
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
/**
* 和 signal 類似,只是將所有的鏈表元素都遷移過去。
*
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
* returns {@code false}
*/
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
/**
* 實現了一個可以響應中斷的等待某個條件的才執行的等待方法。
* 如果 Node 元素關聯的線程被中斷,那麼拋出 InterruptedException。
* 保存鎖的狀態。後續執行釋放鎖時需要比對鎖的狀態,用於判斷是否釋放成功;
* 通過 LockSupport.park(this)的方式阻塞線程。
* 如果線程等待過程中(LockSupport.park(this))被中斷,拋出線程中斷異常(InterruptedException)
*/
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方法,並增加了等待超時時間。
*/
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;
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
if (nanosTimeout <= 0L) {
transferAfterCancelledWait(node);
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 deadline - System.nanoTime();
}
/**
* 同await方法,增加超時的具體時間
*/
public final boolean awaitUntil(Date deadline)
throws InterruptedException {
long abstime = deadline.getTime();
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
boolean timedout = false;
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
if (System.currentTimeMillis() > abstime) {
timedout = transferAfterCancelledWait(node);
break;
}
LockSupport.parkUntil(this, abstime);
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;
}
}