Java 并发编程之 Condition和Object监视器详解
Java 并发编程之 Condition和Object监视器详解
简介
在 Java 程序中,任意一个 Java 对象,都拥有一组监视器方法(定义在 java.lang.Object 类上),主要包括 wait()、wait(long)、notify()、notifyAll() 方法,这些方法与 synchronized 关键字配合,可以实现等待 / 通知模式。Condition 接口也提供了类似 Object 的监视器方法,与 Lock 配合可以实现等待 / 通知模式,但是这两者在使用方式以及功能特性上还是有区别的。Object 的监视器方法与 Condition 接口对比如下:
| 对比项 | Object 监视器方法 | Condition |
|---|---|---|
| 前置条件 | 获取对象的监视器锁 | 调用 Lock.lock() 获取锁调用 Lock.newCondition() 获取 Condition 对象 |
| 调用方法 | 直接调用如:object.wait() | 直接调用如:condition.await() |
| 等待队列个数 | 一个 | 多个 |
| 当前线程释放锁并进入等待队列 | 支持 | 支持 |
| 当前线程释放锁并进入等待队列,在等待状态中不响应中断 | 不支持 | 支持 |
| 当前线程释放锁并进入超时等待状态 | 支持 | 支持 |
| 当前线程释放锁并进入等待状态到将来的某个时间 | 不支持 | 支持 |
| 唤醒等待队列中的一个线程 | 支持 | 支持 |
| 唤醒等待队列中的全部线程 | 支持 | 支持 |
Condition 提供了一系列的方法来对阻塞和唤醒线程:
public interface Condition {
void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();
}
Condition 的方法以及描述如下:
| 方法名称 | 描 述 |
|---|---|
| void await() throws InterruptedException | 当前线程进入等待状态直到被通知(signal)或中断。 |
| void awaitUninterruptibly() | 当前线程进入等待状态直到被通知,该方法不响应中断。 |
| long awaitNanos(long nanosTimeout) throws InterruptedException | 当前线程进入等待状态直到被通知、中断或者超时,返回值表示剩余超时时间。 |
| boolean awaitUntil(Date deadline) throws InterruptedException | 当前线程进入等待状态直到被通知、中断或者到某个时间。如果没有到指定时间就被通知,方法返回 true,否则,表示到了指定时间,返回 false。 |
| void signal() | 唤醒一个等待在 Condition 上的线程,该线程从等待方法返回前必须获得与 Condition 相关联的锁。 |
| void signalAll() | 唤醒所有等待在 Condition 上的线程,能够从等待方法返回的线程必须获得与 Condition 相关联的锁。 |
Condition 的实现分析
可以通过 Lock.newCondition() 方法获取 Condition 对象,而我们知道 Lock 对于同步状态的实现都是通过内部的自定义同步器实现的,newCondition() 方法也不例外,所以,Condition 接口的唯一实现类是同步器 AQS 的内部类 ConditionObject,因为 Condition 的操作需要获取相关的锁,所以作为同步器的内部类也比较合理,该类定义如下:
public class ConditionObject implements Condition, java.io.Serializable
每个 Condition 对象都包含着一个队列(以下称为等待队列),该队列是 Condition 对象实现等待 / 通知功能的关键。
等待队列
等待队列是一个 FIFO 队列,在队列中的每个节点都包含了一个线程引用,该线程就是在 Condition 对象上等待的线程,如果一个线程调用了 Condition.await() 方法,那么该线程将会释放锁、构造成节点加入等待队列并进入等待状态。事实上,节点的定义复用了 AQS 中 Node 节点的定义,也就是说,同步队列和等待队列中节点类型都是 AQS 的静态内部类 AbstractQueuedSynchronized.Node。
一个 Condition 包含一个等待队列,Condition 拥有首节点(firstWaiter)和尾节点(lastWaiter)。当前线程调用 Condition.await() 方法之后,将会以当前线程构造节点,并将节点从尾部加入等待队列,等待队列的基本结构如下图所示:
Condition 拥有首尾节点的引用,而新增节点只需要将原来的尾节点 nextWaiter 指向它,并且更新尾节点即可。上述更新过程不需要 CAS 保证,原因在于调用 await() 方法的线程必定是获取了锁的线程,也就是说该过程是由锁来保证线程安全的。
在 Object 的监视器模型上,一个对象拥有一个同步队列和等待队列,而并发包中的 Lock(更确切地说是同步器)拥有一个同步队列和多个等待队列,其对应关系如下图所示:
Condition 的实现是同步器的内部类,因此每个 Condition 实例都能够访问同步器提供的方法,相当于每个 Condition 都拥有所属同步器的引用。
同步队列:所有尝试获取该对象Monitor失败的线程,都加入同步队列排队获取锁,可以理解为同步队列上的线程都在争用锁对象
等待队列:已经拿到锁的线程在等待其他资源时,主动释放锁,置入该对象等待队列中,等待被唤醒,当调用notify()会在等待队列中任意唤醒一个线程,将其置入同步队列的尾部,排队获取锁,可以理解为等待队列上的线程是主动暂时放弃争用锁
等待
调用 Condition 的 await() 方法会使当前线程进入等待状态,同时线程状态变为等待状态,当从 await() 方法返回时,当前线程一定获取了 Condition 相关联的锁。
如果从队列(同步队列和等待队列)的角度看 await() 方法,当调用 await() 方法时,相当于同步队列的首节点(获取了锁的节点)移动到 Condition 的等待队列中。
Condition 的 await() 方法如下:
public final void await() throws InterruptedException {
// 检测线程中断状态
if (Thread.interrupted())
throw new InterruptedException();
// 将当前线程包装为Node节点加入等待队列
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);
}
调用该方法的线程是成功获取了锁的线程,也就是同步队列中的首节点,该方法会将当前线程构造节点并加入等待队列中,然后释放同步状态,唤醒同步队列中的后继节点,然后当前线程会进入等待状态。
加入等待队列是通过 addConditionWaiter() 方法来完成的:
private Node addConditionWaiter() {
// 尾节点
Node t = lastWaiter;
// 尾节点如果不是CONDITION状态,则表示该节点不处于等待状态,需要清理节点
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
// 根据当前线程创建Node节点
Node node = new Node(Thread.currentThread(), Node.CONDITION);
// 将该节点加入等待队列的末尾
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
如果从队列的角度看,当前线程加入到 Condition 的等待队列,如下图所示:
将当前线程加入到等待队列之后,需要释放同步状态,该操作通过 fullyRelease(Node) 方法来完成:
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(Node) 方法不断自省地检查其对应节点是否在同步队列中:
final boolean isOnSyncQueue(Node node) {
// 节点状态为CONDITION,或者前驱节点为null,返回false
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
// 后继节点不为null,那么肯定在同步队列中
if (node.next != null) // If has successor, it must be on queue
return true;
return findNodeFromTail(node);
}
若节点不在同步队列中,则挂起当前线程,若线程在同步队列中,且获取了同步状态,可能会调用 unlinkCancelledWaiters() 方法来清理等待队列中不为 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;
}
}
通知
调用 Condition 的 signal() 方法,将会唤醒在等待队列中等待时间最长的节点(首节点),在唤醒节点之前,会将节点移到同步队列中。Condition 的 signal() 方法如下所示:
public final void signal() {
// 判断是否是当前线程获取了锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 唤醒等待队列的首节点
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
该方法最终调用 doSignal(Node) 方法来唤醒节点:
private void doSignal(Node first) {
do {
// 把等待队列的首节点移除之后,要修改首结点
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
将节点移动到同步队列是通过 transferForSignal(Node) 方法完成的:
final boolean transferForSignal(Node node) {
// 尝试将该节点的状态从CONDITION修改为0
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 将节点加入到同步队列尾部,返回该节点的前驱节点
Node p = enq(node);
int ws = p.waitStatus;
// 如果前驱节点的状态为CANCEL或者修改waitStatus失败,则直接唤醒当前线程
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
节点从等待队列移动到同步队列的过程如下图所示:
被唤醒后的线程,将从 await() 方法中的 while 循环中退出(因为此时 isOnSyncQueue(Node) 方法返回 true),进而调用 acquireQueued() 方法加入到获取同步状态的竞争中。
成功获取了锁之后,被唤醒的线程将从先前调用的 await() 方法返回,此时,该线程已经成功获取了锁。
Condition 的 signalAll() 方法,相当于对等待队列的每个节点均执行一次 signal() 方法,效果就是将等待队列中的所有节点移动到同步队列中。
相关博客
AbstractQueuedSynchronizer 同步队列详解
AbstractQueuedSynchronizer 独占式同步状态获取与释放
AbstractQueuedSynchronizer 共享式同步状态获取与释放
参考资料
方腾飞:《Java 并发编程的艺术》

浙公网安备 33010602011771号