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; } @Override public 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; } @Override public 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,不然不变,可以看出来,这里的数组作为队列的容器,是循环使用的。