【Java多线程与并发库】11.java5条件阻塞Condition的应用

来源:互联网 发布:网络女神小茉莉 编辑:程序博客网 时间:2024/06/02 13:55
Condition

(1)概述
Condition是Java有关锁的包下面的一个类,锁我们只能实现互斥,不能实现通知。而Condition就是解决这个问题,Condition的功能类似在传统线程技术中的Object.wait和Object.notify的功能。

(2)实例
记得我们之前写过的一个例子,一道面试题:
子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,
接着再回到主线程又循环100次,如此循环50次。

代码是这样的:
package cn.edu.hpu.test;public class ThreadTest4 {    public static void main(String[] args) {        new ThreadTest4().init();    }    public void init()    {        final Business business = new Business();        new Thread(                new Runnable()                {                    public void run() {                        for(int i=0;i<50;i++)                        {                            business.SubThread(i);                        }                                            }                                    }                ).start();                for(int i=0;i<50;i++)        {            business.MainThread(i);        }            }        private class Business    {        boolean bShouldSub = true;//这里相当于定义了控制该谁执行的一个信号灯        public synchronized void MainThread(int i)        {            if(bShouldSub)                try {                    this.wait();                } catch (InterruptedException e) {                    e.printStackTrace();                }                                    for(int j=1;j<=100;j++)            {                System.out.println(Thread.currentThread().getName() + ":i=" + i +",j=" + j);            }            bShouldSub = true;            this.notify();                }                        public synchronized void SubThread(int i)        {            if(!bShouldSub)                try {                    this.wait();                } catch (InterruptedException e) {                    e.printStackTrace();                }                                for(int j=1;j<=10;j++)            {                System.out.println(Thread.currentThread().getName() + ":i=" + i +",j=" + j);            }            bShouldSub = false;                            this.notify();                    }    }}

我们这里要对它进行该写,使用Java5的并发库中的“锁(lock)”和“条件(Condition)”,
来代替原来的“synchronized”和“wait/notify”。
改写后的部分代码:
private class Business{    Lock lock = new ReentrantLock();//线程锁对象    Condition condition = lock.newCondition();//获取锁控制下的状态对象        boolean bShouldSub = true;//这里相当于定义了控制该谁执行的一个信号灯    public void MainThread(int i)    {        lock.lock();//加锁        try {            if (bShouldSub)                try {                    condition.await();//条件阻塞                } catch (InterruptedException e) {                    e.printStackTrace();                }            for (int j = 1; j <= 100; j++) {                System.out.println(Thread.currentThread().getName() + ":i="                        + i + ",j=" + j);            }            bShouldSub = true;            condition.signal();//通知其它某个线程        }finally{            lock.unlock();//解锁        }        }            public synchronized void SubThread(int i)    {        lock.lock();//加锁        try {            if(!bShouldSub)                try {                    condition.await();//条件阻塞                } catch (InterruptedException e) {                    e.printStackTrace();                }                                for(int j=1;j<=10;j++)            {                System.out.println(Thread.currentThread().getName() + ":i=" + i +",j=" + j);            }            bShouldSub = false;                            condition.signal();//通知其它某个线程        }finally{            lock.unlock();//解锁        }    }}

运行效果:


在等待Condition时,允许发生“虚假唤醒”,这通常作为对基础平台语义上的让步。对于大多数应用程序,这带来的实际影响很小,因为Condition应该总是在一个循环中被等待,并测试正在被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序的程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。

为了防止“虚假唤醒”,所以我们要把上面的
if (bShouldSub)    try {        condition.await();//条件阻塞        } catch (InterruptedException e) {        e.printStackTrace();    }    for (int j = 1; j <= 100; j++) {        System.out.println(Thread.currentThread().getName() + ":i="                + i + ",j=" + j);        }}
改为
while(bShouldSub)    try {        condition.await();//条件阻塞        } catch (InterruptedException e) {        e.printStackTrace();    }}for (int j = 1; j <= 100; j++) {    System.out.println(Thread.currentThread().getName() + ":i="            + i + ",j=" + j);}
下面子线程的也是一个道理。

(3)优势
既然condition和原来的this.wait/this.notify实现的效果一样,我干嘛还要使用它呢?
其实condition最重要的一点是,它可以配合lock实现“多路等待”(阻塞/唤醒指定类型的线程),
实现单一this.wait/this.notify它们实现不了的功能。

我们来看看JavaApi中的一个“可阻塞的队列”的底层代码实现:

作为一个示例,假定有一个绑定的缓冲区,它支持 put 和 take 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。而唤醒的时候,“取”的唤醒只会唤醒取take线程,
“放”的唤醒只会唤醒put线程。一次只通知一个线程。可以使用两个 Condition 实例来做到这一点。
 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();     }   } }
如果只有一个Condition,当唤醒的时候,就会唤醒不必要的线程。

上面的队列起到了“缓冲”的效果,就是在存储空间大小固定的时候,
任务来了很多时,多余的任务就会在队列中等待被拿出使用。而存取都是线程安全

的,不会发生栈溢出和空指针异常的情况。

转载请注明出处:http://blog.csdn.net/acmman/article/details/52918211

0 0