上一篇我们解读了 ReentrantLock 的工作原理,ReentrantLock 作为 synchronized 关键字的替代,相对应的线程的通信 wait/notify/notifyAll 也有其替代,这就是 AQS 体系中的 Condition,本篇我们一起来了解一下 Condition 是如何工作的。
1. 源码解读
1.1. await()
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 将当前线程构造成 node 节点并加入 condition 等待队列
Node node = addConditionWaiter();
// 将当前线程持有的锁完全释放,因为会有重入的情况,
// 因此这里是 fullyRelease,重入了多少次就释放多少次
// 这里获取重入的次数,用于后续线程被唤醒并重新获得锁之后恢复锁的状态
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {// 如果当前线程不在同步队列,继续挂起
// 将当前线程挂起
// 这里有一个疑问,线程被 park 之后,是在什么时候被 unpark 的呢?
// 当然是获得锁的线程释放锁之后,通过 unparkSuccessor() 方法唤醒的;
// 这里要结合 lock 的加锁解锁过程进行连贯。
LockSupport.park(this);
// 判断等待挂起的时候,线程有没有被中断过,获得中断状态,用于后面的中断处理
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 代码执行到这里,说明当前线程已经被唤醒,且节点已经在同步队列中
// 被唤醒之后,node 的状态要结合 signal() 方法进行连贯
// 自旋获取锁,并在获取锁之后响应中断
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
// 清除被取消的节点
unlinkCancelledWaiters();
if (interruptMode != 0)
// 响应中断
reportInterruptAfterWait(interruptMode);
}
addConditionWaiter()
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
// 如果尾节点的 waitStatus 不为 condition 等待,说明已被取消,则清除队列中的被取消的节点
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
// 将当前线程构造成节点,加入 condition 等待队列
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
// 这里将遍历链表并清除被取消的节点的过程贴出来,
// 主要是温习一下单向链表的遍历及删除节点的逻辑
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()
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;
}
}
isOnSyncQueue()
// 判断当前线程是否在同步队列中
final boolean isOnSyncQueue(Node node) {
// 如果当前节点的 waitStatus 仍旧为 CONDITION,
// 或者前驱节点不存在,说明当前线程一定不在同步队列。
// 由于同步队列中的头节点代表的是获得锁的线程,
// 因此同步队列中被挂起的线程的节点一定存在前驱节点
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
// 如果当前节点存在后继节点,说明它一定在同步队列中
// 这里需要注意区别 node 所构成的链表的特点:
// node 既可以作为同步队列的节点,也可以作为等待队列的节点;
// 当作为同步队列的节点时,使用两个指针分别指向前驱节点 prev 和后继节点 next;
// 当作为等待队列的节点时,只有一个指向下一个节点的指针 nextWaiter
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.
*/
// 如果上面的条件都不满足,说明节点有可能在同步队列的尾部;
// 或者刚刚由于多个线程的竞争,已经从同步队列尾部升到队列中间;
// 所以下面的逻辑会去遍历同步队列查找当前节点
return findNodeFromTail(node);
}
checkInterruptWhileWaiting()
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
// 如果线程被中断过,尝试将当前线程的 node 转移到同步队列
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
final boolean transferAfterCancelledWait(Node node) {
// 如果可以 CAS 成功,说明 signal() 方法的调用先于线程的中断操作,
// 这时将线程加入同步队列,并返回 true 来标识抛出异常
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.
*/
// 执行到这里说明线程的由于中断被唤醒的,此时线程还未加入同步队列,
// 因此需要让出执行权,等待线程入队后再返回 false 标识线程执行 selfInterrupt() 方法
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
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);
}
}
1.2. signal()
public final void signal() {
// 当前线程不是锁的 owner,抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
// 唤醒等待队列的首节点
doSignal(first);
}
doSignal()
// 该方法努力尝试在等待队列不为空的情况下一定要唤醒一个线程
private void doSignal(Node first) {
do {
// 将 firstWaiter 指针向后移动
// 如果为空,说明等待队列已空
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
// 断开 first 节点的连接
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
transferForSignal()
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
// 将 node 的状态置为初始状态
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).
*/
// 自旋加入同步队列,入队成功后,方法返回的是当前节点的前驱节点,
// 下面需要将这个前驱节点 p 的 waitStatus 状态设置为 SIGNAL,
// 用来标识 p 释放锁之后唤醒后继节点 node
Node p = enq(node);
int ws = p.waitStatus;
// ws > 0 说明前驱节点的状态是 CANCELLED,因此可以直接唤醒当前线程;
// 将 waitstatus 更新成 SIGNAL,如果更新失败直接唤醒当前线程
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
2. 图解
这里我们结合 lock 的加锁与解锁过程,将整个加锁解锁及线程通信串联起来。最终不难发现,这个过程的设计和 synchronized 惊人的相似。
- 只有一个线程 A 获取锁时,可直接获得锁,此时同步队列为空
- 在 A 释放锁之前,线程 B 尝试获取锁,最终会获取失败并加入同步队列
- 在 A 释放锁之前,线程 C 也来尝试获取锁,最终会获取失败也加入同步队列
- 此时在线程 A 中执行了 condition.await() 方法,线程 A 会释放锁,并进入等待队列(在此之前等待队列一直为空),B 和 C 会竞争锁,假设 B 成功获取
- 此时在线程 B 中也执行了 condition.await() 方法,线程 B 会释放锁,并进入等待队列,线程 C 成功获取锁
- 此时在线程 C 中执行了 condition.signal() 方法,线程 A 会被移入同步队列,等待被 C 唤醒后重新获取锁
- 此时线程 C 释放锁,线程 A 获得锁,假设 A 获得锁之后,执行了 condition.signal() 方法,线程 B 会被移入同步队列自旋
- 此时线程 A 释放锁,线程 B 获得锁
- 此时线程 B 释放锁