阅读须知
- JDK版本:1.8
- 文章中使用/* */注释的方法会做深入分析
正文
我们之前分析了AQS独占锁和共享锁的源码实现,接下来我们来分析 AQS 的 ConditionObject,我们常用的 ReentrantLock 的 Condition、ReentrantReadWriteLock 的 Condition 等都是基于 AQS 的 ConditionObject 实现,我们首先来看 ConditionObject 类的成员变量:
AbstractQueuedSynchronizer.ConditionObject:
// condition 队列的第一个节点
private transient Node firstWaiter;
// condition 队列的最后一个节点
private transient Node lastWaiter;
关于节点 Node 的介绍我们在 AQS(AbstractQueuedSynchronizer)源码解析(独占锁)文章中已经进行过详细说明,这里不再重复。我们在分析 ConditionObject 源码时需要把 condition 队列与我们之前分析过的 AQS CLH 队列区分开,CLH 队列是用来排队获取锁的线程的,condition 队列是用来排队在 condition 条件上等待的线程的,两者有不同的作用也是两个不同的队列。
首先我们来看下可以响应中断的 await 方法实现:
AbstractQueuedSynchronizer.ConditionObject:
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
/* 为等待队列添加一个新的节点 */
Node node = addConditionWaiter();
/* 用当前 state 值调用 release 方法释放锁;失败时取消节点并抛出异常 */
int savedState = fullyRelease(node);
int interruptMode = 0;
/* 置于条件队列上的节点如果正在在同步队列上等待重新获取锁,则返回 true */
while (!isOnSyncQueue(node)) {
// 阻塞当前线程
LockSupport.park(this);
/* 取消等待后检查中断,并返回对应的中断模式 */
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 为队列中的线程获取独占不中断模式,如果在等待时中断并且中断模式不是 THROW_IE,则将中断模式设置为 REINTERRUPT
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
/* 断开条件队列中已经为取消状态的等待节点 */
unlinkCancelledWaiters();
if (interruptMode != 0)
/* 根据中断模式做不同的操作 */
reportInterruptAfterWait(interruptMode);
}
我们来描述一下这个方法的流程:
- 如果当前线程中断,则抛出 InterruptedException,响应中断。
- 将当前线程添加到 condition 等待队列中。
- 保存 getState 方法返回的锁定状态。
- 以保存的状态作为参数调用 release 方法释放锁,我们知道,当前线程在调用 await 方法之前首先要获取锁,所以在进入阻塞之前需要把锁释放掉,不然其他线程就没有办法再获取到锁了,如果释放锁失败则抛出 IllegalMonitorStateException 并取消当前节点,失败的原因大概率是调用 await 方法之前没有获取锁。
- while 循环判断当前节点是否在同步队列中等待获取锁,这里进行这个判断的原因有两个,其一,当前线程在进入阻塞状态之前,有可能被其他线程的 signal 操作将当前线程从 condition 队列重新转移到了同步队列,这样就无需进入阻塞状态了,可以直接去尝试重新获取锁;其次,当前线程从阻塞状态被唤醒之后,需要再次进行这个判断,因为我们并不确定这次唤醒是否是正常的 signal 唤醒,正常的 signal 唤醒会将当前线程从 condition 队列重新转移到了同步队列重新尝试获取锁,如果是非正常的唤醒那就要重新进入阻塞状态。
- 如果没有在同步队列中,正常进入阻塞状态直到被唤醒或中断。
- 唤醒后,需要检查线程的中断状态,并做对应的处理。
- 通过以保存的状态作为参数调用 acquire 方法来重新获取锁。
- 如果在步骤6中阻塞时中断,则抛出 InterruptedException。
方法中提到了一个中断模式的概念,初始值为0,在后面的流程中可能变为 THROW_IE 或 REINTERRUPT,我们来解释一下这两个常量:
AbstractQueuedSynchronizer.ConditionObject:
// 此模式意味着重新中断退出等待
private static final int REINTERRUPT = 1;
// 此模式意味着抛出 InterruptedException 退出等待
private static final int THROW_IE = -1;
下面我们来逐一的分析一下每个步骤:
AbstractQueuedSynchronizer.ConditionObject:
private Node addConditionWaiter() {
Node t = lastWaiter;
// 如果条件队列的最后一个节点已经是取消状态,则断开条件队列中已经为取消状态的等待节点
if (t != null && t.waitStatus != Node.CONDITION) {
/* 断开条件队列中已经为取消状态的等待节点 */
unlinkCancelledWaiters();
// 重新取出 lastWaiter
t = lastWaiter;
}
// 为当前线程创建等待节点,等待状态为 CONDITION
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
// 如果到这里条件队列的最后一个节点为 null,说明队列中已经没有任何节点,则将当前节点作为条件队列的第一个节点
firstWaiter = node;
else
// 如果条件队列中还存在等待节点,则将当前节点作为当前最后一个等待节点的下一个节点
t.nextWaiter = node;
// 无论条件队列中是否还有等待节点,到这里都要将当前节点置为条件队列中的最后一个等待节点
lastWaiter = node;
return node;
}
这里提到的节点状态在 AQS 独占锁源码分析的文章中进行过详细的说明,不清楚的读者可以前往查阅了解。
AbstractQueuedSynchronizer.ConditionObject:
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
// 表示下一个遗留下来的状态为 CONDITION 的节点
Node trail = null;
while (t != null) {
// 从条件队列的第一个节点向后遍历
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {
// 如果等待节点为取消状态,则将等待节点的下一个节点置为 null,断开联系
t.nextWaiter = null;
if (trail == null)
// 如果没有遗留下来的节点,则将下一个节点置为条件队列的第一个节点
firstWaiter = next;
else
// 如果有遗留下来的节点,则将下一个节点置为遗留下来的节点的下一个节点
trail.nextWaiter = next;
if (next == null)
// 如果 next 为 null,说明已经遍历到队列的末尾,则将队列的最后一个节点指向当前遗留下来的节点
lastWaiter = trail;
}
else
// 记录遗留下来的状态为 CONDITION 的节点
trail = t;
// 下一个节点继续循环
t = next;
}
}
这个方法主要完成两件事,第一是将所有取消状态的节点断开联系,第二是将所有遗留下来的 CONDITION 状态的节点连接起来,方法中 trail 变量的作用就是完成第二件事。
继续分析await方法中的步骤:
AbstractQueuedSynchronizer.ConditionObject:
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;
}
}
释放锁的操作我们在 AQS 独占锁源码分析的文章中进行过详细分析,这里同样是以独占模式释放锁。
AbstractQueuedSynchronizer.ConditionObject:
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
// 如果节点的等待状态为 CONDITION 或者没有前任节点,证明节点没有再同步队列上等待获得锁
// 因为在同步队列上等待获取锁的节点状态不会为 CONDITION,在条件队列上等待的节点被唤醒后重新加入同步队列尝试获取锁时状态会被改为 0
// 同样在同步队列上等待获取锁的节点的 prev 也不会为空,因为头结点是当前持有锁的线程或者是空节点(初始化状态),等待获取锁的节点至少是头结点的 next,所以等待获取锁的节点的 prev 不会为 null
return false;
if (node.next != null)
// 如果节点有后继节点,证明它一定在同步队列上等待获得锁
return true;
// 从队尾节点向前搜索判断当前节点是否处于同步队列,为什么要这么做呢?到这里并没有办法判断当前节点是否在同步队列中
// 因为可能存在当前节点的前任节点非空但并未放在队列中,因为入队是 CAS 操作,是可能会失败的
// 所以这里需要从队尾向前遍历来确定当前节点是否在同步队列中,因为入队操作总是在队尾插入,所以这里遍历的次数不会很多
return findNodeFromTail(node);
}
AbstractQueuedSynchronizer.ConditionObject:
private int checkInterruptWhileWaiting(Node node) {
/* 如果当前线程已中断,根据中断的时机返回不同的中断模式 */
// 如果当前线程未中断,返回0
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
这里 transferAfterCancelledWait 方法的返回值的含义是这样的:返回 true 代表是被 signal 之前发生的中断,返回 false 代表是被 signal 之后发生的中断。后面我们会看到,checkInterruptWhileWaiting 方法返回 THROW_IE 时,会抛出 InterruptedException 响应中断,返回 REINTERRUPT 时,会重新设置中断标记传播中断中断状态。
为什么要区分这两种处理呢,个人理解,主要是要看中断发生在什么时刻,await 方法本身是响应中断的,所以如果中断的是发生在 await 方法调用期间,那就应该抛出 InterruptedException 响应中断,相反就应该将中断状态传递下去让调用方处理。按照这个思路去理解,在 signal 之前发生的中断实际上是在调用 await 方法期间发生的中断,所以做响应中断处理,而在 signal 之后发生的中断实际上已经不算是调用 await 方法发生的中断了,所以只是做传递中断状态处理。
AbstractQueuedSynchronizer.ConditionObject:
final boolean transferAfterCancelledWait(Node node) {
// CAS 修改节点等待状态,这里如果 CAS 成功,说明可能是线程中断或者超时导致的唤醒
// 也就是在被 signal 唤醒之前取消了等待状态,这时需要将线程重新入队尝试获取锁
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
// CAS 成功将节点转换进入同步队列
enq(node);
return true;
}
// CAS 操作失败,证明 enq 方法正在执行,在这里循环让出 CPU 直到 enq 方法完成,也就是节点已经进入同步队列
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
enq 方法我们在AQS独占锁源码分析的文章中同样分析过。await 方法的下一个步骤是尝试以独占不中断模式为唤醒的节点重新获取锁,也就是 acquireQueued 方法的内容,我们同样在 AQS 独占锁源码分析的文章中分析过,如果再次获取锁失败,会再次阻塞。接下来会断开条件队列中已经为取消状态的等待节点,这个流程上文我们已经分析过。下面我们来看最后一个步骤,根据中断模式来做不同的操作:
AbstractQueuedSynchronizer.ConditionObject:
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
// 上文介绍了 THROW_IE、REINTERRUPT 这两个常量的含义,操作就是在这里实现
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
到这里,await 方法的流程就分析完成了。之前我们提到 await 方法是可以响应中断的,ConditionObject 同样提供了不响应中断的 awaitUninterruptibly 方法,它的流程与 await 方法非常相似,这里不过多赘述,下面我们来看一下可以指定等待时间的 await 方法的另一个重载的版本:
AbstractQueuedSynchronizer.ConditionObject:
public final boolean await(long time, TimeUnit unit)
throws InterruptedException {
long nanosTimeout = unit.toNanos(time);
if (Thread.interrupted())
throw new InterruptedException();
// 为等待队列添加一个新的节点
Node node = addConditionWaiter();
// 用当前 state 值调用 release 方法;返回保存的状态。取消节点并在失败时抛出异常
int savedState = fullyRelease(node);
// 超时时间节点
final long deadline = System.nanoTime() + nanosTimeout;
// 标记是否超时
boolean timedout = false;
int interruptMode = 0;
// 置于条件队列上的节点如果正在在同步队列上等待重新获取锁,则返回 true
while (!isOnSyncQueue(node)) {
if (nanosTimeout <= 0L) {
// 进入这里说明已经超过了等待时间,将节点转换进入同步队列并退出循环
timedout = transferAfterCancelledWait(node);
break;
}
if (nanosTimeout >= spinForTimeoutThreshold)
// 阻塞指定的时间
LockSupport.parkNanos(this, nanosTimeout);
// 取消等待后检查中断,并返回对应的中断模式
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
// 超时时间点减去当前系统时间即为需要等待的最大时间
nanosTimeout = deadline - System.nanoTime();
}
// 为队列中的线程获取独占不中断模式,如果在等待时中断并且中断模式不是 THROW_IE,则将中断模式设置为 REINTERRUPT
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
// 断开条件队列中已经为取消状态的等待节点
unlinkCancelledWaiters();
if (interruptMode != 0)
// 根据中断模式做不同的操作
reportInterruptAfterWait(interruptMode);
// 返回是否超时
return !timedout;
}
可以看到,方法中的大多数逻辑都与响应中断的 await 方法相同,唯一不同的就是这里使用用户指定的超时时间来完成超时等待这个功能。
最后我们来分析一下唤醒操作:
AbstractQueuedSynchronizer.ConditionObject:
public final void signal() {
// 需要在持有独占锁的前提下进行
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
/* 从条件队列的第一个节点开始找到第一个不为取消状态的节点进行唤醒 */
doSignal(first);
}
这里对当前线程是否持有独占锁的判断方法 isHeldExclusively 由子类实现,如果不支持 condition,默认抛出 UnsupportedOperationException。
AbstractQueuedSynchronizer.ConditionObject:
private void doSignal(Node first) {
do {
// 将头结点置为给定头结点的下一个等待节点,并判断是否为 null
if ( (firstWaiter = first.nextWaiter) == null)
// 将条件队列的最后一个节点置为 null 标识当前条件队列已经没有等待节点
lastWaiter = null;
// 将头结点的下一个节点置为 null 断开联系
first.nextWaiter = null;
/* 循环直到找到一个 CONDITION 状态的节点并将其成功从 condition 队列转移到同步队列或者遍历到了为 null 的节点 */
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
AbstractQueuedSynchronizer.ConditionObject:
final boolean transferForSignal(Node node) {
// CAS 改变节点等待状态,如果没有成功,说明节点已经被取消了
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 将节点拼入同步队列并返回前任节点
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
// 如果前任节点已取消或尝试设置前任节点的 waitStatus 为 SIGNAL 失败,则唤醒节点的线程重新同步
LockSupport.unpark(node.thread);
return true;
}
ConditionObject 还提供了可以唤醒全部等待节点的 signalAll 方法,与 signal 方法的区别在于后者只唤醒一个节点,前者会唤醒 condition 队列中的所有等待节点,流程非常相似,这里不过多赘述。
到这里,AQS ConditionObject 源码分析就完成了。