Java并发编程札记-(四)JUC锁-04Condition简介

时间:2021-06-29 18:00:01

我们已经学习了如何通过使用锁来同步两个任务,但为了解决某个问题,任务之间只有互斥是不够的,还需要相互通信,相互协作。今天就来学习如何实现任务之间的协作。

初识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并发编程更多内容请参考: