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

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()后被其它线程重新唤醒后,是继续往下运行。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值