我们已经学习了如何通过使用锁来同步两个任务,但为了解决某个问题,任务之间只有互斥是不够的,还需要相互通信,相互协作。今天就来学习如何实现任务之间的协作。
初识Condition
在任务协作中,关键问题是任务之间的通信。握手可以通过Object的监视器方法(wait()和notify()/notifyAll())和synchronized方法和语句来安全地实现。Java SE5的JUC提供了具有await()和signal()/signalAll()方法的Condition和Lock来实现。其中,Lock代替了synchronized方法和语句,Condition代替了Object的监视器方法。本文介绍Condition。
我们可以通过Condition的await()方法挂起一个任务。当外部条件发生变化,意味着某个任务应该继续执行时,可以通过signal()来通知某个任务,从而唤醒一个任务,或者调用signal()来唤醒所有在这个Condition上被其自身挂起的任务。听起来Condition的这些方法和Object的监视器方法(wait()和notify()/notifyAll())没有区别。与Object相比,Condition可以更精细地控制线程的休眠与唤醒。Condition实际上被绑定在Lock上,可使用Lock实例的newCondition方法获取Condition实例。所以我们可以创建多个Condition,在不同的情况下使用不同的Condition。
下面通过JavaDoc中的一个例子来演示下Condition是如何更精细地控制线程的休眠与唤醒的。
public class BoundedBuffer {
final Lock lock = new ReentrantLock();//锁
final Condition notFull = lock.newCondition();//写条件
final Condition notEmpty = lock.newCondition();//读条件
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)//如果队列已满
notFull.await();//阻塞写线程
items[putptr] = x;
if (++putptr == items.length)
putptr = 0;
++count;
notEmpty.signal();//唤醒读线程
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)//如果队列已空
notEmpty.await();//阻塞读线程
Object x = items[takeptr];
if (++takeptr == items.length)
takeptr = 0;
--count;
notFull.signal();//唤醒写线程
return x;
} finally {
lock.unlock();
}
}
}
这是一个有界的缓冲区,支持put(Object)与take()方法。put(Object)负责向缓冲区中存数据,take负责从缓冲区中读数据。在多线程环境下,调用put(Object)方法,当缓冲区已满时,会阻塞写线程,如果缓冲区不满,则写入数据,并唤醒读线程。调用take()方法时,当缓冲区为空,会阻塞读线程,如果缓冲区不空,则读取数据,并唤醒写线程。
这就是多个Condition的强大之处,假设缓存队列已满,那么阻塞的肯定是写线程,唤醒的肯定是读线程,相反,阻塞的肯定是读线程,唤醒的肯定是写线程。如果采用Object类中的wait(), notify(), notifyAll()实现该缓冲区,当向缓冲区写入数据之后需要唤醒读线程时,通过notify()或notifyAll()无法明确的指定唤醒读线程,而只能通过notifyAll唤醒所有线程,但notifyAll无法区分唤醒的线程是读线程,还是写线程。 如果唤醒的是写线程,那么线程刚被唤醒,又被阻塞了,这样就降低了效率。
方法列表
//方法摘要
void await()
//造成当前线程在接到信号或被中断之前一直处于等待状态。
boolean await(long time, TimeUnit unit)
//造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
long awaitNanos(long nanosTimeout)
//造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
void awaitUninterruptibly()
//造成当前线程在接到信号之前一直处于等待状态。
boolean awaitUntil(Date deadline)
//造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。
void signal()
//唤醒一个等待线程。
void signalAll()
//唤醒所有等待线程。
与Object监视器监视器方法的比较
对比项 | Condition | Object监视器 | 备注 |
---|---|---|---|
使用条件 | 获取锁 | 获取锁,创建Condition对象 | |
等待队列的个数 | 一个 | 多个 | |
是否支持通知指定等待队列 | 支持 | 不支持 | |
是否支持当前线程释放锁进入等待状态 | 支持 | 支持 | |
是否支持当前线程释放锁并进入超时等待状态 | 支持 | 支持 | |
是否支持当前线程释放锁并进入等待状态直到指定最后期限 | 支持 | 不支持 | |
是否支持唤醒等待队列中的一个任务 | 支持 | 支持 | |
是否支持唤醒等待队列中的全部任务 | 支持 | 支持 |
本文就讲到这里,想了解Java并发编程更多内容请参考: