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

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

初识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监视器监视器方法的比较

对比项ConditionObject监视器备注
使用条件获取锁获取锁,创建Condition对象
等待队列的个数一个多个
是否支持通知指定等待队列支持不支持
是否支持当前线程释放锁进入等待状态支持支持
是否支持当前线程释放锁并进入超时等待状态支持支持
是否支持当前线程释放锁并进入等待状态直到指定最后期限支持不支持
是否支持唤醒等待队列中的一个任务支持支持
是否支持唤醒等待队列中的全部任务支持支持

本文就讲到这里,想了解Java并发编程更多内容请参考:

  • Java并发编程札记-目录
    原文作者:java并发
    原文地址: http://blog.csdn.net/panweiwei1994/article/details/78818860
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞