前言
ArrayBlockingQueue也是BlockingQueue接口的实现类,从它的命名就能猜出来,它底层是用数组实现的,不同于LinkedBlockingQueue的链表构造。
实现原理
首先来看它的要害属性:
// 寄存元素的数组final Object[] items;// 记录下次take操作的数组地位int takeIndex;// 记录下次put操作的数组地位int putIndex;// 数组长度int count;// 操作数组的锁final ReentrantLock lock;// 数组非空条件private final Condition notEmpty;// 数组未满条件private final Condition notFull;
再来看它的重要办法:
offer()
public boolean offer(E e) { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lock(); try { // 队列满了,则返回false if (count == items.length) return false; else { // 队列没满,则入队 enqueue(e); return true; } } finally { lock.unlock(); }}
put()
public void put(E e) throws InterruptedException { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { // 数组满了则阻塞期待,用while不必if,是为了防止虚伪唤醒 while (count == items.length) notFull.await(); // 队列未满时入队 enqueue(e); } finally { lock.unlock(); }}
poll()
public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { // 数组为空返回null,否则返回移除的元素 return (count == 0) ? null : dequeue(); } finally { lock.unlock(); }}
take()
public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { // 数组为空则阻塞期待 while (count == 0) notEmpty.await(); // 数组不为空时返回移除的元素 return dequeue(); } finally { lock.unlock(); }}
与LinkedBlockingQueue相似,take()、put()是阻塞的,poll()、offer()是非阻塞的。
其中,enqueue()、dequeue()办法如下:
private void enqueue(E x) { // assert lock.getHoldCount() == 1; // assert items[putIndex] == null; final Object[] items = this.items; items[putIndex] = x; if (++putIndex == items.length) putIndex = 0; count++; notEmpty.signal();}
private E dequeue() { // assert lock.getHoldCount() == 1; // assert items[takeIndex] != null; final Object[] items = this.items; @SuppressWarnings("unchecked") E x = (E) items[takeIndex]; // 移除元素时,要手动将takeIndex地位处的元素置为null,让它被垃圾回收掉,否则会导致内存透露 items[takeIndex] = null; // help GC if (++takeIndex == items.length) takeIndex = 0; count--; if (itrs != null) itrs.elementDequeued(); notFull.signal(); return x;}
因为数组的个性,ArrayBlockingQueue并没有像LinkedBlockingQueue那样应用2把锁,而是应用takeIndex和putIndex记录下一次入队/出队操作的数组地位。并且takeIndex,putIndex,count三个变量都没有用volatile润饰或者Atomic原子类,因为他们的批改都在加锁后的环境中进行的,曾经保障了线程平安。
参考资料:
《Java并发编程之美》