Condition 實現原理
說 Codition 前,需要說下 ConditioObject。
ConditionObject 是同步器 AbstractQueuedSynchronzied 的內部類,因爲 Condition 的操作需要關聯的鎖。 ArrayBlockingQueue 就是 Condition 的具體應用。 Object 中其實 也有 wait ,notify ,notifyAll 等操作, Condition 相當於將 wait ,notify ,notifyAll 轉換成想要的對象,將比較難懂的同步操作變成直觀可控的對象行爲。
應用場景 ArrayBlockingQueue
ArrayBlockingQueue 的構造函數。
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
通過構造函數,可以看到 Condition 的創建時需要關聯鎖的。
從隊列中去取出(take)數據 。
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
往隊列中加入數據 enqueue
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
可以看主要用了 await signal 等方法。具體代表什麼含義?
Condition 實現主要包含三個部分:等待隊列、等待、通知。
如果瞭解 AQS 原理可以知道, AQS 中有個同步隊列的概念。
等待隊列
等待隊列和同步隊列類似,都是一個 FIFO 隊列。隊列上每個節點包含一個線程引用,該線程就是 Condition 對象上的等待線程。等待隊列結構如下:
Condition 等待隊列,也是包含首節點(firstWaiter),和尾節點(tailWaiter),如果一個線程調用了 Condition.await() 方法。那麼該線程將會釋放鎖,並以當前線程構造節點加入等待隊列並進入等待狀態。
Object 監視器模型
Object 監視器模型 包含了一個同步多路和多個等待隊列,結構如下所示:
等待
當調用 Condition 的 await() 方法(或者以 await開頭的方法),會使得當前線程進入等待隊列,並且釋放鎖,同時線程的狀態變爲等待狀態。
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 當前線程加入等待隊列
Node node = addConditionWaiter();
// 釋放同步狀態,也就是釋放鎖
int savedState = fullyRelease(node);
int interruptMode = 0;
// node 不在節點中會一直 park 阻塞下去。達到等待的效果。
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);
}
調用該方法的線程成功獲得了鎖的線程,也就是同步隊列的首節點,該方法將會將該線程構造成節點並加入等待隊列中,然後釋放同步狀態,喚醒同步隊列中的後續節點,然後當前節點會進入等待狀態。要注意的是,如果等地隊列中的節點被喚醒,喚醒節點的線程開始嘗試獲取同步狀態。但是如果不是通過 Condition.signal 進行喚醒的,而是對等待線程進行中斷,那麼會拋出 InterruptedException。
調用 Condition signal 方法後,當前線程會加入到等待隊列,如下圖所示:
通知
調用 Condition.signal() 方法,將會喚醒等待隊列中等待時間最長的節點(首節點),在喚醒節點之前,會將節點移動到同步隊列中。
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
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).
*/
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
需要注意的是,調用該方法的前置條件是當前線程必須獲得了鎖,可以看到 Signal() 方法進行了
isHeldExclusively 檢查,判斷是否獲得了鎖,接着獲取等待隊列的首節點,將其移動到同步隊列並使用 LockSupport 喚醒節點中的線程。
節點從等待隊列,移動到同步隊列的操作過程如下:
通過調用同步器的 enq(Node node) 方法,等待隊列中的頭節點線程安全地移動到同步隊列中,當節點移動到同步隊列後,當前線程再使用 LockSupport 喚醒該節點的線程。
被喚醒的線程,將從 await() 方法中的 while 循環中退出。從 await 方法看
// 當前節點已經在同步隊列了,不會在循環下去了
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
隨後調用同步器的 acquireQueued() 方法加入到同步隊列的競爭中。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
成功獲取同步狀態(獲得鎖)之後,被喚醒的線程,景從先前調用的 await 方法返回。此時線程已經成功獲得了鎖。
總結
本文剖析了一下 Condition 的實現原理,等待隊列,等待,通知的實現原理。