ArrayBlockingQueue 源码深度分析
ArrayBlockingQueue 是 Java 并发包(java.util.concurrent)中基于数组实现的有界阻塞队列,核心设计围绕「循环队列 + 独占锁 + 条件变量」展开,保证线程安全的同时支持阻塞式入队 / 出队操作。本文将从核心特性、类结构、源码实现、关键机制等维度全面解析。
一、核心特性
- 有界性:底层基于固定容量的数组,初始化时必须指定容量,且后续不可修改;
- FIFO 顺序:遵循「先进先出」原则,入队从队尾插入,出队从队首移除;
- 阻塞机制:
- 队列满时,入队线程阻塞(
put方法);队列空时,出队线程阻塞(take方法); - 支持超时阻塞(
offer(timeout)、poll(timeout))和非阻塞(offer、poll)操作;
- 队列满时,入队线程阻塞(
- 线程安全 :通过
ReentrantLock独占锁保证所有操作的原子性,支持公平 / 非公平锁策略; - 元素非空 :不允许插入
null元素,否则抛出NullPointerException; - 内存友好:数组结构内存连续,缓存命中率高,延迟较低。
二、类结构与核心成员变量
2.1 类继承关系
java
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
// ... 核心代码
}
- 继承
AbstractQueue:复用队列基础方法(如add、isEmpty); - 实现
BlockingQueue:提供阻塞式入队 / 出队接口; - 实现
Serializable:支持序列化(需注意 transient 修饰的锁和条件变量需手动序列化)。
2.2 核心成员变量
java
// 底层存储元素的数组(final 修饰,容量不可变)
private final Object[] items;
// 出队索引:下一个要被 take/poll 的元素位置
private int takeIndex;
// 入队索引:下一个要被 put/offer 的元素位置
private int putIndex;
// 队列中当前元素个数(无需原子类,因所有操作都在锁保护下)
private int count;
// 独占锁:控制入队/出队的线程安全(公平/非公平)
private final ReentrantLock lock;
// 条件变量:队列非空时唤醒等待出队的线程
private final Condition notEmpty;
// 条件变量:队列非满时唤醒等待入队的线程
private final Condition notFull;
// 迭代器相关(弱一致性迭代器)
transient Itrs itrs = null;
- 关键设计:
takeIndex/putIndex:实现循环队列的核心,通过「取模运算」循环复用数组空间;lock:单锁设计(入队 / 出队共用同一把锁),简化锁管理但降低并发吞吐量;notEmpty/notFull:基于 Condition 的等待 - 通知机制,精准唤醒阻塞线程。
三、构造方法:初始化核心组件
ArrayBlockingQueue 提供 3 个构造方法,核心是初始化数组、锁和条件变量,并支持初始元素填充。
3.1 指定容量(默认非公平锁)
java
public ArrayBlockingQueue(int capacity) {
this(capacity, false); // 默认非公平锁
}
3.2 指定容量 + 公平性策略
java
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity]; // 初始化固定容量数组
this.lock = new ReentrantLock(fair); // 公平/非公平锁
this.notEmpty = lock.newCondition(); // 绑定锁的条件变量
this.notFull = lock.newCondition();
}
- 公平性影响:
- 公平锁(
fair=true):线程获取锁的顺序遵循「等待队列顺序」,避免饥饿,但锁竞争时性能略低; - 非公平锁(
fair=false):线程可「插队」获取锁,吞吐量更高,但可能导致部分线程饥饿。
- 公平锁(
3.3 指定容量 + 公平性 + 初始元素
java
public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c) {
this(capacity, fair);
final ReentrantLock lock = this.lock;
lock.lock(); // 初始化时加锁,避免并发问题
try {
int i = 0;
try {
for (E e : c) {
checkNotNull(e); // 检查元素非空
items[i++] = e; // 填充数组
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException("Collection size exceeds queue capacity");
}
count = i; // 设置元素个数
putIndex = (i == capacity) ? 0 : i; // 更新入队索引(循环处理)
} finally {
lock.unlock();
}
}
- 初始化时直接加锁,确保填充元素过程中无并发干扰;
- 若集合大小超过队列容量,抛出
IllegalArgumentException。
四、核心方法源码解析
ArrayBlockingQueue 的核心逻辑集中在「入队」「出队」「删除」三大操作,均基于锁保护和循环队列实现。
4.1 入队操作
入队相关方法:put(阻塞)、offer(非阻塞 / 超时)、enqueue(私有核心实现)。
4.1.1 put(E e):阻塞入队(队列满时等待)
java
public void put(E e) throws InterruptedException {
checkNotNull(e); // 禁止插入 null
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // 可中断锁(响应线程中断)
try {
// 队列满时,阻塞当前线程,等待 notFull 信号
while (count == items.length)
notFull.await(); // 释放锁,进入等待队列
enqueue(e); // 核心入队逻辑
} finally {
lock.unlock(); // 确保锁释放
}
}
- 关键细节:
lockInterruptibly():线程在等待锁时可被中断(抛出InterruptedException);while (count == items.length):用循环而非if,避免「虚假唤醒」(Condition 可能被无故唤醒,需重新检查条件)。
4.1.2 offer(E e):非阻塞入队(队列满时直接返回)
java
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();
}
}
4.1.3 offer(E e, long timeout, TimeUnit unit):超时阻塞入队
java
public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
checkNotNull(e);
long nanos = unit.toNanos(timeout); // 转换为纳秒
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length) {
if (nanos <= 0)
return false; // 超时返回 false
// 等待指定时间,返回剩余等待时间(<=0 表示超时)
nanos = notFull.awaitNanos(nanos);
}
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
4.1.4 enqueue(E x):私有核心入队逻辑
java
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(); // 唤醒等待出队的线程(队列从空变非空)
}
- 循环队列的核心:
putIndex自增后通过== items.length判断是否需要「绕圈」。
4.2 出队操作
出队相关方法:take(阻塞)、poll(非阻塞 / 超时)、dequeue(私有核心实现)。
4.2.1 take():阻塞出队(队列空时等待)
java
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 队列空时,阻塞当前线程,等待 notEmpty 信号
while (count == 0)
notEmpty.await();
return dequeue(); // 核心出队逻辑
} finally {
lock.unlock();
}
}
4.2.2 poll():非阻塞出队(队列空时返回 null)
java
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 队列为空返回 null,否则出队
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
4.2.3 dequeue():私有核心出队逻辑
java
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex]; // 取出 takeIndex 位置的元素
items[takeIndex] = null; // 置空,帮助 GC
// 出队索引循环:到达数组末尾则回到 0
if (++takeIndex == items.length)
takeIndex = 0;
count--; // 元素个数-1
// 迭代器弱一致性处理(若有迭代器正在遍历,通知元素已出队)
if (itrs != null)
itrs.elementDequeued();
notFull.signal(); // 唤醒等待入队的线程(队列从满变非满)
return x;
}
4.3 删除操作:remove(Object o)
数组实现的队列删除非队首元素效率较低(需遍历 + 移动元素):
java
public boolean remove(Object o) {
if (o == null) return false;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count > 0) {
final int putIndex = this.putIndex;
int i = takeIndex;
// 循环遍历队列(从 takeIndex 到 putIndex)
do {
if (o.equals(items[i])) {
removeAt(i); // 核心删除逻辑
return true;
}
if (++i == items.length)
i = 0;
} while (i != putIndex);
}
return false;
} finally {
lock.unlock();
}
}
removeAt(int removeIndex):删除指定索引元素
java
private void removeAt(final int removeIndex) {
final Object[] items = this.items;
// 情况1:删除的是当前出队索引(takeIndex)
if (removeIndex == takeIndex) {
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
} else {
// 情况2:删除中间元素(需移动后续元素填补空位)
final int putIndex = this.putIndex;
// 循环移动元素:从 removeIndex 到 putIndex-1
for (int i = removeIndex;;) {
int next = i + 1;
if (next == items.length)
next = 0;
if (next != putIndex) {
items[i] = items[next];
i = next;
} else {
items[i] = null;
this.putIndex = i; // 更新入队索引(因元素前移)
break;
}
}
count--;
if (itrs != null)
itrs.removedAt(removeIndex);
}
notFull.signal(); // 删除元素后队列有空位,唤醒入队线程
}
- 性能说明:删除中间元素时需遍历移动后续元素,时间复杂度为 O (n),不适用于高频删除非队首元素的场景。
4.4 查看操作:peek()
获取队首元素但不删除(非阻塞):
java
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return itemAt(takeIndex); // 直接返回 takeIndex 位置元素(可能为 null)
} finally {
lock.unlock();
}
}
// 辅助方法:获取指定索引元素(队空时返回 null)
private E itemAt(int i) {
return (E) items[i];
}
五、关键机制解析
5.1 循环队列实现
ArrayBlockingQueue 通过 takeIndex、putIndex 和 count 三个变量实现循环队列:
- 入队:
putIndex从 0 开始递增,到达数组长度后重置为 0; - 出队:
takeIndex从 0 开始递增,到达数组长度后重置为 0; - 队列满:
count == items.length(无需通过putIndex + 1 == takeIndex判断,简化逻辑); - 队列空:
count == 0。
5.2 锁与条件变量的协同
- 锁 :
ReentrantLock保证入队、出队、删除等操作的原子性,同一时间只有一个线程能操作队列; - 条件变量:
notEmpty:当队列从空→非空时,通过signal()唤醒等待出队的线程;notFull:当队列从满→非满时,通过signal()唤醒等待入队的线程;
- 等待 - 通知流程:
- 生产者线程调用
put时队列满 → 调用notFull.await()释放锁,进入等待队列; - 消费者线程调用
take出队后 → 调用notFull.signal()唤醒一个生产者线程; - 生产者线程被唤醒后,重新获取锁,检查队列是否非满,然后入队。
- 生产者线程调用
5.3 弱一致性迭代器
ArrayBlockingQueue 的迭代器(iterator())是弱一致性的:
- 迭代器创建时会快照当前队列的
takeIndex、count等状态; - 迭代过程中,其他线程修改队列(如入队、出队、删除),迭代器可能不会立即反映最新状态,也不会抛出
ConcurrentModificationException; - 核心原因:迭代器遍历期间不持有锁,仅通过
itrs记录元素变化(如elementDequeued()、removedAt()),保证迭代过程不崩溃,但不保证数据实时性。
六、性能对比与适用场景
6.1 与 LinkedBlockingQueue 对比
| 特性 | ArrayBlockingQueue | LinkedBlockingQueue |
|---|---|---|
| 底层结构 | 数组(连续内存) | 链表(离散节点) |
| 容量 | 有界(必须指定容量) | 可选有界(默认 Integer.MAX_VALUE) |
| 锁设计 | 单锁(入队 / 出队共用) | 双锁(takeLock/putLock) |
| 并发吞吐量 | 较低(单锁竞争) | 较高(双锁减少竞争) |
| 延迟 | 较低(缓存命中率高) | 较高(节点分配 / 回收开销) |
| 删除非队首元素 | O (n)(数组移动) | O (1)(链表节点删除) |
6.2 适用场景
- 适合「有界队列」场景(如限流、避免内存溢出);
- 适合生产者 - 消费者模型中,元素入队 / 出队频繁、删除操作少的场景;
- 对延迟敏感、缓存友好的场景(数组连续内存优势)。
七、注意事项
- 禁止插入 null :所有入队方法都会调用
checkNotNull(e),插入null会抛出NullPointerException; - 阻塞可中断 :
put、take、offer(timeout)、poll(timeout)均支持线程中断,需处理InterruptedException; - 公平性选择:非公平锁吞吐量更高,公平锁避免饥饿,需根据业务场景选择;
- 序列化 :队列支持序列化,但锁(
ReentrantLock)和条件变量(Condition)是 transient 的,序列化时仅保存数组、索引、元素个数等状态,反序列化后重新初始化锁和条件变量。
总结
ArrayBlockingQueue 是「有界阻塞队列」的经典实现,核心设计是「数组循环队列 + ReentrantLock + Condition」:
- 数组保证内存连续,提升缓存命中率;
- 循环队列复用数组空间,避免扩容开销;
- 单锁 + 条件变量简化线程同步,保证线程安全;
- 支持阻塞 / 非阻塞 / 超时操作,适配不同并发场景。
其设计权衡了简单性和性能,适合需要严格限制队列大小、低延迟的生产者 - 消费者场景。