Java多线程/并发21、利用Condition来实现阻塞队列

来源:互联网 发布:淘宝网板鞋 编辑:程序博客网 时间:2024/05/15 14:19

Java文档上的例子,利用Condition来实现阻塞队列。
假设有一个固定大小的缓冲区(缓冲区是队列,遵守FIFO),支持存和取方法。如果缓冲区为空时尝试取数据,那么线程将阻塞,直到缓冲区有一个可用的数据;如果缓冲区满了,这时尝试写数据,那么线程将被阻塞直到有可用空间。我们希望有两个独立的等待集(Condition阻塞队列),一个放置存数据的等待线程,一个放置取数据的等待线程,这样,当缓冲区有空位置的时侯,可以到“存数据的线程等待集”中唤醒一个线程存数据;当缓冲区从一无所有到有数据存入时,可以到“取数据的线程等待集”中唤醒一个线程取数据。这个关系不能搞错!
Condition是由Lock创建的,每个Lock锁可以创建多个Condition对象。只有同一把锁的Condition对象可以相互影响

public class ConditionDemo {    static class BoundedBuffer {        final Lock lock = new ReentrantLock();        final Condition notFull = lock.newCondition();// 存数据的线程等待集——因为满了而等待        final Condition notEmpty = lock.newCondition();// 取数据的线程等待集——因为空了而等待        /* 用数组实现缓冲队列(FIFO) */        final Object[] items = new Object[5];        /*         * putptr:存数据的索引位置         * takptr:取数据的索引位置          * count:当前队列大小         */        int putptr, takeptr, count;        public void put(Object x) throws InterruptedException {            lock.lock();/* 给需要同步的代码加锁 */            try {                /*如果数组满了,当前线程进入存数据的等待集。不用if是怕线程假醒*/                while (count == items.length) {                    System.err.println(Thread.currentThread().getName()+"写入数据["+putptr+"]异常:缓冲池已满");                    notFull.await();                }                /* 数据写入 */                System.out.println(Thread.currentThread().getName()+"写入数据["+putptr+"]:"+String.valueOf(x));                items[putptr] = x;                /* 改变写入索引位置+1(为下次写入数据准备),如果超出数组边界,则重置为数组起始点 */                if (++putptr == items.length)                    putptr = 0;                /* 增加数组有效果元素计数,即:队列大小加1 */                ++count;                /* 至此队列中肯定有可用数据了,从“取数据的线程等待集”中唤醒线程 */                notEmpty.signal();            } finally {                /* lock 必须在 finally 块中释放。否则,如果受保护的代码将抛出异常,锁就有可能永远得不到释放 */                lock.unlock();            }        }        public Object take() throws InterruptedException {            lock.lock();/* 给需要同步的代码加锁 */            try {                /*如果数组为空,当前线程进入读数据的等待集。不用if是怕线程假醒*/                System.err.println("线程预备");                while (count == 0){                    System.err.println(Thread.currentThread().getName()+"读取数据["+putptr+"]异常:缓冲池为空");                    notEmpty.await();                }                System.err.println("线程开始读数据");                /*读取数据*/                Object x = items[takeptr];                System.out.println(Thread.currentThread().getName()+"获取队列数据["+takeptr+"]:"+x);                /* 改变读取索引位置+1(为下次读数据准备),如果超出数组边界,则重置为数组起始点 */                if (++takeptr == items.length)                    takeptr = 0;                /* 减少数组有效果元素计数,即:队列大小减1 */                --count;                /* 至此队列中肯定有可用空间了,从“存数据的线程等待集”中唤醒线程 */                notFull.signal();                return x;            } finally {                lock.unlock();            }        }    }    public static void main(String[] args) {        final BoundedBuffer boundbuffe =new BoundedBuffer();        for(int i=0;i<10;i++){            new Thread(new Runnable() {                @Override                public void run() {                    try {                        Thread.sleep(new Random().nextInt(3000));                        int temp=new Random().nextInt(1000);                        boundbuffe.put(temp);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }).start();        }        for(int i=0;i<5;i++){            new Thread(new Runnable() {                @Override                public void run() {                    try {                        Thread.sleep(new Random().nextInt(500));                        boundbuffe.take();                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }).start();        }    }}

例子不难理解,我用中文注释了一下。调整存、取线程的开启数量和sleep时间,可以轻松制造“写数据异常”
运行产生的结果(部分截图):
这里写图片描述

想到一个问题,condition的await()方法让一个线程等待后,等到这个线程被唤醒时,它是接着往下执行呢?还是refresh,重新执行整个方法或lock块呢?
为了求证,在take()方法内用两个输出包围notEmpty.await()代码块,变成:

/*如果数组为空,当前线程进入读数据的等待集不用if是怕线程假醒*/System.out.println(Thread.currentThread().getName()+"线程预备");while (count == 0){    System.err.println(Thread.currentThread().getName()+"读取数据["+putptr+"]异常:缓冲池为空");    notEmpty.await();}System.out.println(Thread.currentThread().getName()+"线程开始读数据");

重新运行结果:
这里写图片描述

“线程预备”这句话只出现了一次,表明如果线程await()后被其它线程重新唤醒后,是继续往下运行。

0 0
原创粉丝点击