详解Java中的ArrayBlockingQueue
ArrayBlockingQueue 是 Java 中 java.util.concurrent 包下的一个阻塞队列实现,底层基于数组,是线程安全的。它是一个 有界队列,需要在创建时指定容量大小。此类的主要特性包括:
-
线程安全:
-
使用 独占锁(ReentrantLock) 和 条件变量 来实现线程安全的操作。
-
-
先进先出(FIFO):
-
元素按插入顺序排列,头部是最先插入的元素,尾部是最后插入的元素。
-
-
-
队列容量固定,不能超过初始化时指定的大小。
-
核心构造方法
public ArrayBlockingQueue(int capacity)
-
创建一个指定容量的队列。
public ArrayBlockingQueue(int capacity, boolean fair)
-
fair: 决定锁的公平性,true表示公平锁,false为非公平锁(性能更高)。
public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c)
-
初始化队列,并将给定集合中的所有元素按迭代顺序添加到队列。
核心字段
private final Object[] items; // 存储队列元素的数组 private int takeIndex; // 指向队列头部(下一个被取出的元素) private int putIndex; // 指向队列尾部(下一个插入的位置) private int count; // 队列中当前的元素个数 private final ReentrantLock lock; // 独占锁,保证线程安全 private final Condition notEmpty; // 条件变量:队列不为空的等待条件 private final Condition notFull; // 条件变量:队列未满的等待条件
关键方法解读
1. offer(E e)
尝试向队列中插入元素(非阻塞方法)。如果队列已满,返回 false。
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock(); // 获取锁
try {
if (count == items.length) // 队列已满
return false;
enqueue(e); // 插入元素
return true;
} finally {
lock.unlock(); // 释放锁
}
}
2. put(E e)
向队列中插入元素,如果队列已满,当前线程阻塞直到队列有空间。
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // 可中断获取锁
try {
while (count == items.length) // 队列已满,等待 notFull 信号
notFull.await();
enqueue(e); // 插入元素
} finally {
lock.unlock(); // 释放锁
}
}
3. take()
从队列中取出一个元素,如果队列为空,当前线程阻塞直到有可用元素。
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // 可中断获取锁
try {
while (count == 0) // 队列为空,等待 notEmpty 信号
notEmpty.await();
return dequeue(); // 获取队头元素
} finally {
lock.unlock(); // 释放锁
}
}
4. peek()
非阻塞方法,返回队列头部元素但不移除。如果队列为空,返回 null。
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock(); // 获取锁
try {
return (count == 0) ? null : itemAt(takeIndex); // 获取头部元素
} finally {
lock.unlock(); // 释放锁
}
}
底层核心方法
1. enqueue(E e)
向队列尾部插入元素。
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x; // 在 putIndex 位置插入元素
if (++putIndex == items.length) // 如果到达数组尾部,回到起始位置(循环数组)
putIndex = 0;
count++; // 更新队列元素个数
notEmpty.signal(); // 唤醒等待 notEmpty 的线程
}
2. dequeue()
从队列头部取出元素。
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex]; // 获取队列头部的元素
items[takeIndex] = null; // 释放队列头部的引用
if (++takeIndex == items.length) // 如果到达数组尾部,回到起始位置
takeIndex = 0;
count--; // 更新队列元素个数
notFull.signal(); // 唤醒等待 notFull 的线程
return x;
}
线程安全性
-
使用 独占锁 来控制对队列的并发访问。
-
条件变量
notEmpty和notFull分别控制取元素和插入元素的等待逻辑。 -
锁的公平性(可选)决定线程获取锁的顺序,公平锁更公平但性能稍差。
应用场景
-
生产者-消费者模型
:
-
生产者线程向队列插入元素,消费者线程从队列取出元素。
-
-
任务调度
:
-
用于线程池中存储和分发任务。
-
-
流量控制
:
-
通过有界性限制队列中的元素数量,防止内存溢出。
-
示例
public static void main(String[] args) {
ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
// 生产者线程
new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) {
queue.put(i);
System.out.println("Produced: " + i);
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// 消费者线程
new Thread(() -> {
try {
while (true) {
Integer value = queue.take();
System.out.println("Consumed: " + value);
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}

浙公网安备 33010602011771号