ArrayBlockingQueue位于java.util.concurrent并发包中,是从JDK1.5之后添加的。以下解析均基于目前最新的JDK8.0版本。
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable
从这里我们可以看出,ArrayBlockingQueue继承自AbstractQueue,并实现了BlockingQueue接口。我们可以将ArrayBlockingQueue拆分成三段,即Array,Blocking,Queue来分别进行解析。
首先是Array,我们可以看到在ArrayBlockingQueue中有一个字段如下:
/** The queued items */
final Object[] items;
说明ArrayBlockingQueue的底层是用数组实现的。
下面再来看看如何解析Blocking,也就是ArrayBlockingQueue是如何实现阻塞的,看源码:
/*
* Concurrency control uses the classic two-condition algorithm
* found in any textbook.
*/
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
也就是说ArrayBlockingQueue是通过ReentrantLock(可重入锁)和2个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();
enqueue(e);
} finally {
lock.unlock();
}
}
首先获取锁,之后在while循环中检测队列是否已满,如果已满,则调用notFull条件变量的await方法进行等待,如果未满,则调用enqueue方法进行入队操作,最后再释放锁。需要注意的是,在入队操作中还有一个重要的步骤,我们看一下具体代码:
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();
}
前面部分都是正常的入队操作,关键是最后一句:notEmpty.signal(),这一句的作用就是唤醒那些在notEmpty条件变量上等待的线程,因为入队操作之前很可能队列是空的,所以如果在这之前有线程访问队列并试图进行take操作的话会进行阻塞。而一旦入队了,则应该对那些阻塞的线程进行唤醒。我们看到在上面的put操作中有notFull.await();很显然我们可以推断出在与put相对应的take操作中,如果队列为空的话会有notEmpty.await()操作。源码如下:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
如果take操作的时候队列不空的话,则调用dequeue操作进行出队,我们可以推断,在与上面enqueue操作相对的dequeue操作中的最后面会发送队列未满的信号:notFull.signal()去唤醒阻塞在put操作中notFull.await()操作处的线程。dequeue操作源码如下:
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
重点关注倒数第二句:notFull.signal(),即出队之后通知其他阻塞在notFull条件变量上的线程,队列现在未满,你们可以继续往里面put了。
综上所述,ArrayBlockingQueue中的take操作和put操作利用了ReentrantLock和2个Condition来实现阻塞。抽象出来就是:
Condition notEmpty;
Condition notFull;
take() { //从队列中取
while(队列为空) {
notEmpty.await(); //可以理解为,我要一直阻塞到notEmpty条件得到满足
}
dequeue(); //出队操作
}
dequeue() {
出队;
notFull.signal(); //可以理解为,已经出队了一个元素,让notFull条件得到满足。
}
put() { //往队列中添加
while(队列已满) {
notFull.await(); //可以理解为,我要一直阻塞到notFull条件得到满足。
}
enqueue();
}
enqueue() {
入队;
notEmpty.signal(); //可以理解为,已经有元素入队了,让notEmpty条件得到满足。
}
上面伪代码省略了获取锁和释放锁的过程。关于Condition条件变量的含义,可能我理解的也不太准确,下次专门写一篇来学习一下Condition。
已经解析完了ArrayBlockingQueue三部分中的前面两部分,最后解析一下Queue的部分,这部分没有太多好解析的,就是一个队列而已,有出队和入队操作,需要说明的是ArrayBlockingQueue中有多组出队入队操作,但是它们之间是有所不同的:
以下4个方法都是入队操作。
- add(E e)操作,如果队列已满则会抛出异常
- offer(E e) 操作,不会阻塞,立即返回,如果队列已满则返回false
- offer(E e, long timeout, TimeUnit unit) 如果队列已满,会先阻塞一段时间,之后若还是满的,则返回false
- put(E e) 如果队列满则会一直阻塞直到有空间可以入队。
以下三个方法都是出队操作。
- poll()操作,与offer(E e) 操作相对应,不会阻塞,立即返回,如果队列为空则返回null,否则返回队首元素。
- poll(long timeout, TimeUnit unit)与offer(E e, long timeout, TimeUnit unit)操作相对应,若队列为空则会阻塞一定时间,若一段时间后仍为空则返回null
- take()操作与put(E e)操作相对应,会一直阻塞直到队列不为空。
小结一下:
- take(出队)和put(入队)是一对
- poll(出队)和offer(入队)是一对
- poll(带时间参数)和offer(带时间参数)是一对
- add(入队)
水平有限,如有不当之处,还望指正!