Java并发学习笔记(七)-ArrayBlockingQueue

来源:互联网 发布:淘宝试用如何领取 编辑:程序博客网 时间:2024/06/05 07:57

Java并发包concurrent里提供了3个阻塞队列,ArrayBlockingQueue,LinkedBlockingQueue,PriorityBlockingQueue。阻塞队列通常用于生产消费模式,满队列时生产者阻塞,空队列时消费者阻塞。ArrayBlockingQueue是一个有界的阻塞队列,此队列按FIFO原则增加删除元素。底层实现是一个数组。

public class ArrayBlockingQueueDemo {public static void main(String[] args) {ExecutorService es = Executors.newCachedThreadPool();BlockingQueue<Bread> queue = new ArrayBlockingQueue<Bread>(10);for (int i = 0; i < 2; i++) {es.execute(new Baker(queue));}for (int i = 0; i < 10; i++) {es.execute(new BreadConsumer(queue));}es.shutdown();}}class Baker implements Runnable {private static int no = 0;private int id = ++no;private int count = 0;private BlockingQueue<Bread> queue;public Baker(BlockingQueue<Bread> queue) {this.queue = queue;}@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println("面包师" + id + "正准备做第" + ++count + "面包");Bread bread = new Bread();// 满队列情况下,阻塞try {queue.put(bread);System.out.println("面包师" + id + "做的第" + count + "面包是面包"+ bread.getId());} catch (InterruptedException e) {}}}}class BreadConsumer implements Runnable {private static int no = 0;private int id = ++no;private int count = 0;private BlockingQueue<Bread> queue;public BreadConsumer(BlockingQueue<Bread> queue) {this.queue = queue;}@Overridepublic void run() {for (int i = 0; i < 2; i++) {System.out.println("顾客 " + id + "准备买第" + ++count + "个面包");// 空队列情况下,阻塞try {Bread bread = queue.take();System.out.println("顾客" + id + "买到的第" + count + "个面包是面包"+ bread.getId());} catch (InterruptedException e) {}}}}class Bread {private static int count = 0;private int id = ++count;public int getId() {return id;}}

该类也提供了其它一些非阻塞的方法,如add,offer,pull等。看看它的实现。

构造函数

    public ArrayBlockingQueue(int capacity, boolean fair) {        if (capacity <= 0)            throw new IllegalArgumentException();        this.items = new Object[capacity];        lock = new ReentrantLock(fair);        notEmpty = lock.newCondition();        notFull =  lock.newCondition();    }
capacity是初始化时数组长度,fair是表示是否为公平锁(这里不介绍这个)。我们可以看到,在构造函数里,有一个Object数组,保存队列元素,有两个lock的condition,分别表示非空队列条件及非满队列条件。

再看阻塞插入方法put

    public void put(E e) throws InterruptedException {        checkNotNull(e);        final ReentrantLock lock = this.lock;        lock.lockInterruptibly();        try {            while (count == items.length)                notFull.await();            insert(e);        } finally {            lock.unlock();        }    }

正式插入元素之前,使用可中断的lock锁。count表示当前队列里的元素个数。如果队列元素个数等于数组长度,那么非满队列条件阻塞,等待队列非满时唤醒。如果队列不满,则插入。

    private void insert(E x) {        items[putIndex] = x;        putIndex = inc(putIndex);        ++count;        notEmpty.signal();    }
insert方法,在队列尾部插入一个元素,重新对putIndex计算,队列元素个数加1,并在最后唤醒非空队列条件,表示可以从队列中取出元素。

再来看阻塞方法take

    public E take() throws InterruptedException {        final ReentrantLock lock = this.lock;        lock.lockInterruptibly();        try {            while (count == 0)                notEmpty.await();            return extract();        } finally {            lock.unlock();        }    }
与put类似,获取锁,再判断队列是否为空,空队列,则非空队列条件等待,刚才的insert方法在插入一个元素后,会唤醒这个条件。如果队列不为空,则取出一个元素。

    private E extract() {        final Object[] items = this.items;        E x = this.<E>cast(items[takeIndex]);        items[takeIndex] = null;        takeIndex = inc(takeIndex);        --count;        notFull.signal();        return x;    }
extract方法从队列头部读取一个元素,并置数组中该元素为null,防止内存泄露,队列元素个数减1,并唤醒非满队列条件,说明已经不是满队列,可以插入元素了。


offer和pull的实现也比较相似,只是没有阻塞这个动作

    public E poll() {        final ReentrantLock lock = this.lock;        lock.lock();        try {            return (count == 0) ? null : extract();        } finally {            lock.unlock();        }    }

我们在insert和extract里看到inc(int)方法。

    final int inc(int i) {        return (++i == items.length) ? 0 : i;    }
如果当前索引等于数组长度,则置为0,不然不变,可以看出来,这里的数组作为队列的容器,是循环使用的。

0 0
原创粉丝点击