ArrayBlockingQueue 是 Java 并发包中一个基于数组实现的有界阻塞队列,其核心设计围绕 ReentrantLock 和两个 Condition 条件变量展开,用于高效地解决多线程环境下的生产者-消费者问题。
📊 底层数据结构
ArrayBlockingQueue 的核心字段定义清晰体现了其数据结构:
final Object[] items:一个定长数组,用于存储队列元素。它在构造时初始化,容量一旦确定便不可更改。int takeIndex:指向下一个被取出元素(队头)的索引。int putIndex:指向下一个被插入元素(队尾)的索引。int count:当前队列中的元素数量,用于判断队列是否为空或已满。final ReentrantLock lock:一个可重入锁,用于控制所有并发访问,是线程安全的基础。private final Condition notEmpty:条件变量,用于消费者线程在队列为空时等待。private final Condition notFull:条件变量,用于生产者线程在队列已满时等待。
该队列在逻辑上是一个循环数组。当 putIndex 或 takeIndex 到达数组末尾时,会通过取模运算回到数组的起始位置(0),从而高效地利用数组空间,实现先进先出(FIFO)的顺序。
⚙️ 核心方法解析
ArrayBlockingQueue 的核心方法都依赖于 lock 和 Condition 来实现线程间的同步与阻塞。
1. 入队方法:put(E e)
put 方法用于向队列尾部插入元素。如果队列已满,它会阻塞当前生产者线程,直到队列有空闲空间。
public void put(E e) throws InterruptedException {
Objects.requireNonNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // 获取可中断锁
try {
while (count == items.length) // 如果队列已满
notFull.await(); // 在 notFull 条件上等待
enqueue(e); // 将元素插入队列
} finally {
lock.unlock();
}
}
流程解析:
- 获取锁:调用
lock.lockInterruptibly()获取锁,此操作可响应中断。 - 检查队列满:在
while循环中检查count == items.length。使用while而非if是为了防止虚假唤醒。 - 等待:若队列已满,调用
notFull.await(),线程被阻塞并释放锁,进入notFull的等待队列。 - 入队:当被唤醒且队列不满时,调用
enqueue(e)执行入队操作。 - 释放锁:无论操作成功与否,都在
finally块中释放锁。
2. 出队方法:take()
take 方法用于从队列头部取出并移除元素。如果队列为空,它会阻塞当前消费者线程,直到队列中有元素可用。
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) // 如果队列为空
notEmpty.await(); // 在 notEmpty 条件上等待
return dequeue(); // 从队列中取出元素并返回
} finally {
lock.unlock();
}
}
流程解析:
- 获取锁:同样通过
lock.lockInterruptibly()获取锁。 - 检查队列空:检查
count == 0。 - 等待:若队列为空,调用
notEmpty.await()阻塞线程,进入notEmpty的等待队列。 - 出队:当被唤醒且队列非空时,调用
dequeue()执行出队操作并返回元素。 - 释放锁:在
finally块中释放锁。
3. 非阻塞方法:offer(E e) 与 poll()
除了阻塞方法,该类还提供了非阻塞的替代方案:
offer(E e):尝试立即插入元素,如果队列已满,则直接返回false,不会阻塞。poll():尝试立即取出元素,如果队列为空,则直接返回null,不会阻塞。
🎯 核心用途与场景
ArrayBlockingQueue 是实现生产者-消费者模式的经典工具,其核心价值在于解耦、缓冲和流量控制。
- 生产者-消费者协作:作为两者之间的缓冲区,自动调节速度差异。队列满时生产者阻塞,队列空时消费者阻塞,避免了忙等待,提升了 CPU 利用率。
- 流量控制:由于是有界队列,可以用来限制系统中待处理任务的数量,防止资源过载,对内存敏感的应用场景尤其适用。
- 任务调度与线程池:常用作线程池(如
ThreadPoolExecutor)的工作队列,存储待执行的任务,由工作线程消费。 - 系统解耦:生产者和消费者无需知道对方的具体实现,只需与队列交互,提高了系统的可维护性和可扩展性。
⚖️ 公平性策略与性能考量
- 公平性选择:构造时可指定
fair参数。默认非公平策略吞吐量更高;公平策略(fair=true)则保证等待线程按 FIFO 顺序获取锁,减少线程饥饿,但会降低吞吐量。 - 性能权衡:使用单锁设计,入队和出队操作互斥,高并发下可能成为瓶颈。但其存储空间预先分配,使用时内存开销较小,性能更稳定。
- 与
LinkedBlockingQueue的对比:ArrayBlockingQueue强制有界,内存预分配,单锁控制;LinkedBlockingQueue默认无界(可指定),动态分配节点,通常使用双锁,在高并发下可能有更好的吞吐量,但内存开销可能更大且性能波动更明显。
总的来说,ArrayBlockingQueue 是一个设计精良、用途明确的并发组件。它的有界特性和阻塞机制使其成为资源受限的多线程场景中实现稳定、高效数据交换的理想选择。
