本文部分摘自《Java 并发编程的艺术》node
任意一个 Java 对象,都拥有一个监视器方法,主要包括 wait()、wait(long timeout)、notify() 以及 notifyAll() 方法,这些方法与 synchronized 同步关键字配合,能够实现等待 - 通知模式。Condition 接口也提供了相似 Object 的监视器方法,与 Lock 配合能够实现等待 - 通知模式编程
Object 的监视器方法与 Condition 接口的对比:数组
对比项 | Object 监视器方法 | Condition |
---|---|---|
前置条件 | 获取对象的监视器锁 | 调用 Lock.lock() 获取锁调用 Lock.newCondition() 获取 Condition 对象 |
调用方法 | 直接调用如:object.wait() | 直接调用如:condition.await() |
等待队列个数 | 一个 | 多个 |
当前线程释放锁并进入等待队列 | 支持 | 支持 |
当前线程释放锁并进入等待队列,在等待状态中不响应中断 | 不支持 | 支持 |
当前线程释放锁并进入超时等待状态 | 支持 | 支持 |
当前线程释放锁并进入等待状态到未来的某个时间 | 不支持 | 支持 |
唤醒等待队列中的一个线程 | 支持 | 支持 |
唤醒等待队列中的所有线程 | 支持 | 支持 |
Condition 定义了等待 - 通知两种类型的方法,当前线程调用这些方法时,须要提早获取到 Condition 对象关联的锁。Condition 对象是由 Lock 对象(调用 Lock 对象的 newCondition() 方法)建立,换句话说,Condition 是依赖 Lock 对象的安全
public class ConditionUserCase { Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); public void conditionWait() throws InterruptedException { lock.lock(); try { condition.await(); } finally { lock.unlock(); } } public void conditionSignal() { lock.lock(); try { condition.signal(); } finally { lock.unlock(); } } }
当调用 await() 方法后,当前线程会释放锁并在此等待,而其余线程调用 Condition 对象的 signal() 方法,通知当前线程后,当前线程才从 await() 方法返回,而且在返回前已经获取了锁并发
Condition 的部分方法以及描述:ide
方法名称 | 描 述 |
---|---|
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 的使用方式ui
public class BoundedQueue<T> { private Object[] items; // 添加的下标,删除的下标和数据当前数量 private int addIndex, removeIndex, count; private Lock lock = new ReentrantLock(); private Condition notEmpty = lock.newCondition(); private Condition notFull = lock.newCondition(); public BoundedQueue(int size) { items = new Object[size]; } /** * 添加一个元素,若是数组满,则添加线程进入等待状态,直到有空位 */ public void add(T t) throws InterruptedException { lock.lock(); try { while (count == items.length) { notFull.await(); } items[addIndex] = t; if (++addIndex == items.length) { addIndex = 0; } ++count; notEmpty.signal(); } finally { lock.unlock(); } } /** * 由头部删除一个元素,若是数组空,则删除线程进入等待状态,直到有新元素添加 */ @SuppressWarnings("unchecked") public T remove() throws InterruptedException { lock.lock(); try { while (count == 0) { notEmpty.await(); } Object x = items[removeIndex]; if (++removeIndex == items.length) { removeIndex = 0; } --count; notFull.signal(); return (T) x; } finally { lock.unlock(); } } }
ConditionObject 是同步器 AbstractQueuedSynchronizer 的内部类,每一个 Condition 对象都包含着一个队列(等待队列),该队列是 Condition 对象实现等待 - 通知功能的关键this
等待队列是一个 FIFO 队列,在队列中的每一个节点都包含了一个线程引用,该线程就是在 Condition 对象上等待的线程,若是一个线程调用了 Condition.await() 方法,那么该线程就会释放锁,构形成节点并加入等待队列并进入等待状态线程
一个 Condition 包含一个等待队列,Condition 拥有首尾节点的引用,新增节点只须要将原有的尾节点 nextWaiter 指向它,并更新尾节点便可。节点引用更新的过程并无使用 CAS 来保证,缘由在于调用 await() 方法的线程一定是获取了锁的线程,也就是该过程是由锁来保证线程安全的对象
在 Object 的监视器模型上,一个对象拥有一个同步队列和等待队列,而并发包中的 Lock 拥有一个同步队列和多个等待队列,其对应关系如图所示:
调用 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) unlinkCancelledWaiters(); // 对等待线程中断,会抛出异常 if (interruptMode != 0) reportInterruptAfterWait(interruptMode); }
调用 Condition 的 signal() 方法,将会唤醒在等待队列中等待时间最长的节点(首节点),在唤醒节点以前,会将节点移到同步队列中
Condition 的 signal() 方法代码以下所示:
public final void signal() { // 检查当前线程是否获取了锁 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); // 获取等待队列首节点,移动到同步队列并唤醒 Node first = firstWaiter; if (first != null) doSignal(first); }
Condition 的 signAll() 方法,至关于对等待队列中的每一个结点均执行一个 signal() 方法,效果就是将等待队列中全部节点所有移动到同步队列中,并唤醒每一个节点的线程