Condition 接口提供了类似Object的监视器方法,与Lock配合可以实现 等待/通知 模式
Condition的接口
在说Condition的接口之前,先对比一下与Object监视器的异同:
对比项 | Object的监视器(Monitor) | Condition |
---|---|---|
前置条件 | 获取对象的锁 | 调用Lock.lock()获取锁 调用Lock.newCondition获取Condition对象 |
调用方式 | 直接调用:object.wait() | 直接调用:condition.await() |
等待队列个数 | 一个 | 多个 |
当前线程释放锁并进入等待状态 | 支持 | 支持 |
当前线程释放锁并进入等待状态,在等待状态中不响应中断 | 不支持 | 支持 |
当前线程释放锁并进入超时等待状态 | 支持 | 支持 |
当前线程释放锁并进入超时等待状态到将来某一个时间 | 不支持 | 支持 |
唤醒等待队列中的一个线程 | 支持 | 支持 |
唤醒等待队列中的全部线程 | 支持 | 支持 |
Condition接口的实现在两个队列同步器:AbstractQueuedSynchronizer和AbstractQueuedLongSynchronizer中,它们通过一个内部类ConditionObject聚合了其中的操作。下面来看看Condition提供的API有哪些:
// 调用该方法线程会释放锁并进入等待状态。调用该方法的线程要想进入运行状态的情况有:
// 其他线程调用该Condition的signal()或signalAll()方法,当前线程被选中唤醒
// 1. 其他线程中断当前线程
// 2. 如果当前等待线程从await()方法返回,表名该线程已经获取了Condition对象对应的锁
void await() throws InterruptedException;
// 对中断不敏感
void awaitUninterruptibly();
// 当前线程进入等待状态,直到被通知、中断或者超时才返回。返回值表示剩余的时间
long awaitNanos(long nanosTimeout) throws InterruptedException;
// 当前线程进入等待状态,直到被通知、中断或者到某一个时间。到了指定时间返回true,否则返回false
boolean awaitUntil(Date deadline) throws InterruptedException;
// 唤醒一个等待在Condition上的线程,该线程从等待方法返回前必须获得与Condition相关联的锁
void signal();
// 唤醒所有等待在Condition上的线程,能够从等待方法返回的线程必须与Condition相关联的锁
void signalAll();
下面的示例展示了Condition的基本用法:
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException {
lock.lock();
try {
condition.await()
} finally {
lock.unlock();
}
}
public void conditionSignal() throws InterruptedException {
lock.lock();
try {
condition.signal()
} finally {
lock.unlock();
}
}
Condition的实现
ConditionObject 是同步器 AQS 的内部类,每个 Condition 对象都包含着一个队列,称为等待队列,该队列是 Condition 对象实现等待/通知功能的关键。
1. 等待队列
等待队列是一个FIFO的队列,在队列中每个节点都包含了一个线程引用,该线程就是在 Condition 对象上等待的线程,如果一个线程调用了 Condition.await() 方法,那么该线程将会释放锁、构造成节点并加入等待队列进入等待状态。一个Condition包含一个等待队列,Condition拥有首节点(firstWaiter)和尾节点(lastWaiter),当前线程调用 Condition.await() 方法,将会以当前线程构造节点,并将节点从尾部加入等待队列。
在节点的更新过程中,并不需要CAS的保证,因为调用await()方法的线程必定是已经获取了锁的线程,不会出现线程安全问题,该过程是由锁的机制来保证的。
在Object的监视器模型上,一个对象拥有一个同步队列和等待队列,而并发包中的Lock(同步器)拥有一个同步队列和多个等待队列,如下图:
2. 等待
当调用 await() 方法时,相当于同步队列的首节点释放了同步状态,从同步队列中移出,并构造成新的节点移动到Condition的等待队列中
下面是 await() 方法的源码:
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()干了些什么事儿:
-
当前线程为获取了锁的线程(同步队列中的首节点)
-
将当前线程构造成新的节点(等待节点),并加入等待队列
-
释放该线程的同步状态,唤醒同步对列中的后继节点,然后该线程在等待队列中等待
3. 通知
调用Condition的signal()方法,将唤醒在等待队列中等待时间最长的节点(首节点),在唤醒节点之前,会将节点移到同步队列中。signal() 的源码如下:
public final void signal() {
// 如果当前线程没有获取到锁则抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 获取等待队列中的首节点
Node first = firstWaiter;
// 如果首节点不为空,将其移到同步队列,并调用LockSupport唤醒节点中的线程
if (first != null)
doSignal(first);
}
节点从等待队列移动到同步队列的过程如下:
成功获取同步状态后,被唤醒的线程将从先前调用的await()方法返回,此时该线程已经成功获取了锁。Condition的signalAll()方法,相当于对等待队列中的每一个节点均执行一次signal()方法,其结果就是将等待队列中的所有节点全部移入到同步队列中,并唤醒每个节点的线程。
参考
《Java并发编程的艺术》