ArrayBlockingQueue源码解析
- 前言
ArrayBlockingQueue是一个由数组组成的有界阻塞队列,ArrayBlockingQueue创建的时候必须指定容量。这个队列按照先进先出(FIFO)的顺序排列。队列的头(head)是队列中等待时间最长的一个元素。队列的尾(tail)是队列中等待时间最短的元素。新元素被插入在队列的尾部,从队列的head中检索获取元素。这个队列有固定的大小,包含生产者生产的元素和消费者提取的元素。队列一旦创建成功,那么就不可以改变容量了。尝试往一个已经满了的队列插入元素,将会导致插入操作被阻塞,同样的尝试从一个为空的队列抽取元素,也会导致抽取操作被阻塞。这个队列支持一个可选的公平策略对等待的生产者和消费者线程进行排序。默认情况下能保证顺序,如果将fair设置成true,则能保证先进先出的顺序。公平策略通常会降低吞吐量,但是减低了可变性并且避免一直获取不到元素的情况的发生。 - 类继承关系
来看看这个类的变量
/** 队列数组*/
final Object[] items;
/** 下一个可以被取走元素在数组中的索引位置 */
int takeIndex;
/** 下一个可以添加元素的索引位置 */
int putIndex;
/** 当前队列中元素的个数 */
int count;
/** 主要的锁守卫所有的访问,在获取元素或者添加元素都必须获取该锁才能执行造作 */
final ReentrantLock lock;
/** 为等待获取元素的线程提供不同空的信号 */
private final Condition notEmpty;
/** 为等待添加元素的线程提供队列不满的信号 */
private final Condition notFull;
可以看到takeIndex putIndex count这些变量并没有使用volatile关键字,那是因为队列所有的操作都要成功获取lock的情况下才可以修改这些数据的,所以不加volatile也是安全的。
来看看构造函数
看到构造函数必须要指定容量,boolean代表是否使用公平策略,默认是非公平策略,也可以在创建的时候将一个Collection集合整合进队列中,但是这个集合的元素的数量必须不能大于队列的大小,否则会发生异常ArrayIndexOutOfBoundsException。
来看看添加元素的一种情况,put
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();// 通过ReentrantLock尝试获取锁
try {
while (count == items.length)// 获取锁之后,判断当前队列是否已经满了
notFull.await();// 如果满了的话,等待notFull收到signal
enqueue(e);// 当前队列没有满的话,则入队列
} finally {
lock.unlock();// 解锁
}
}
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)// 添加元素后发现已经满了,则这设置下一个可以放置元素的位置为0
putIndex = 0;
count++;
notEmpty.signal();// 为等待获取元素的线程发送唤醒信号
}
在来看看获取元素的方法 take
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)// 当前队列为空的话,则等待不为空的signal
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
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();// 发送队列不为空的信号,通知获取元素的等待线程加入lock的等待队列。
return x;
}
这个队列不支持并发读写元素,而且不支持动态扩容,但是比较稳定。