概述
ArrayBlockingQueue是一个基于数组实现的有界阻塞队列。遵循先进先出FIFO原则对元素进行排序,元素都是从尾部入队,头部出队。
1、数据结构
基于数组的阻塞队列,必须设置长度,元素不允许为空,先进先出。
2、原理
ArrayBlockingQueue,读写用一把锁,也就意味着所有加锁操作都是互斥的。当队列已满时,put线程会阻塞,当队列为空时,take线程会阻塞。
3、源码解读
3.1 变量
ArrayBlockingQueue
阻塞队列中定义存储数据的数组item
、下一个要取出元素的索引takeIndex
、下一个要加入的元素索引putIndex
、队列中元素的个数count
,一把锁lock
、读写对应的两个condition
对象notFull 、notEmpty以及维护数组序列的itrs。
3.2 构造方法
ArrayBlockingQueue
的构造方法有三个,分别如下:
- 设置容量大小,未指定是否使用公平锁,默认使用非公平锁;
- 设置容量大小,且指定锁类型;
- 将Collection集合中的元素导入队列,其实用的就是put(E x)方法,后面将put方法时详述,这里不做详细介绍。
3.3 put(E x)入队方法
入队按顺序,队列已满,则进入入队等待队列等待。
public void put(E e) throws InterruptedException {
//元素判空
checkNotNull(e);
final ReentrantLock lock = this.lock;
//获取独占锁,和lock()方法的区别是让它在阻塞时也可抛出异常跳出
lock.lockInterruptibly();
try {
//如果队列已满,进入入队等待线程队列等待
while (count == items.length)
notFull.await();
//入队,详见下面的入队操作
enqueue(e);
} finally {
//释放锁
lock.unlock();
}
}
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x;
//队列已满,设置putIndex为0
if (++putIndex == items.length)
putIndex = 0;
count++;//队列中元素个数加1
notEmpty.signal();//通知出队等待队列中的线程,队列不为空
}
3.4 take(E x)出队方法
出队按顺序,队列为空,则进入出队等待线程队列等待。
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//如果队列为空,则进入出队等待线程队列等待
while (count == 0)
notEmpty.await();
// 详见下面的出队方法
return dequeue();
} finally {
lock.unlock();
}
}
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
//队列中最后一个元素取走,则从0号位开始取
if (++takeIndex == items.length)
takeIndex = 0;
count--;/当前拥有元素个数减1
// 维护itrs参数,比较复杂,有兴趣的可以深入研究
if (itrs != null)
itrs.elementDequeued();
//通知入队等待队列中的线程,队列中有位置空出来了
notFull.signal();
return x;
}
3.4 迭代器
由于数组带有下标,迭代器也维护了元素的位置索引,两者有关联关系,所以每次去除元素后,都需要维护Iterator迭代器中的参数。有兴趣的可以深入研究迭代器原理。
总结
ArrayBlockingQueue相对来讲比较简单,维护itrs部分比较难,有兴趣的可以研究。其特点如下:
- 元素不允许为空,为空会抛NullPointException异常;
- 有界队列,必须设置容量大小;
- 底层数据结构为数组;
- 读写用同一把锁,所以是安全类
- 读写同一把锁,也就说明了读写不能同时进行,但由于其基于数组实现,天然的效率很高;
- 每次take操作都需维护Iterator迭代器中的参数;