阻塞队列是线程安全的队列,核心特性:
- 入队阻塞:队列满时,生产者线程阻塞,直到队列有空闲位置
- 出队阻塞:队列空时,消费者线程阻塞,直到队列有元素
- 是生产者-消费者模型的核心组件
我会带你实现最经典的有界阻塞队列 (基于数组+锁+等待/通知机制),这也是 JDK ArrayBlockingQueue 的核心原理。
一、核心实现原理
- 数据结构:固定大小的数组存储元素
- 线程安全 :使用
ReentrantLock保证原子操作 - 阻塞唤醒 :
Condition条件队列(比wait/notify更精准)notEmpty:队列非空,唤醒消费者notFull:队列非满,唤醒生产者
二、完整代码实现
java
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 基于数组实现的有界阻塞队列
* @param <E> 队列元素类型
*/
public class MyBlockingQueue<E> {
// 存储元素的数组
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;
/**
* 构造方法:指定队列容量
* @param capacity 队列最大容量
*/
public MyBlockingQueue(int capacity) {
if (capacity <= 0) {
throw new IllegalArgumentException("容量必须大于0");
}
this.items = new Object[capacity];
// 默认非公平锁(也可传true创建公平锁)
this.lock = new ReentrantLock();
this.notEmpty = lock.newCondition();
this.notFull = lock.newCondition();
}
/**
* 阻塞入队:队列满时阻塞线程
* @param element 入队元素
* @throws InterruptedException 线程中断异常
*/
public void put(E element) throws InterruptedException {
// 加锁:保证原子操作
lock.lockInterruptibly();
try {
// 循环判断:防止虚假唤醒
while (count == items.length) {
// 队列满,生产者阻塞,释放锁等待唤醒
notFull.await();
}
// 入队操作
enqueue(element);
} finally {
// 必须释放锁
lock.unlock();
}
}
/**
* 阻塞出队:队列空时阻塞线程
* @return 出队元素
* @throws InterruptedException 线程中断异常
*/
public E take() throws InterruptedException {
lock.lockInterruptibly();
try {
// 循环判断:防止虚假唤醒
while (count == 0) {
// 队列空,消费者阻塞,释放锁等待唤醒
notEmpty.await();
}
// 出队操作
return dequeue();
} finally {
lock.unlock();
}
}
/**
* 私有入队方法:仅在加锁后调用
*/
private void enqueue(E element) {
items[putIndex] = element;
// 指针循环(数组环形复用)
if (++putIndex == items.length) {
putIndex = 0;
}
count++;
// 唤醒一个等待的消费者
notEmpty.signal();
}
/**
* 私有出队方法:仅在加锁后调用
*/
private E dequeue() {
E element = (E) items[takeIndex];
items[takeIndex] = null; // 帮助GC回收
// 指针循环
if (++takeIndex == items.length) {
takeIndex = 0;
}
count--;
// 唤醒一个等待的生产者
notFull.signal();
return element;
}
// 获取队列元素数量
public int size() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
三、关键细节解释
1. 为什么用 while 而不是 if?
必须用循环判断 ,防止虚假唤醒(线程未被唤醒却自动唤醒),保证队列状态正确。
2. 环形数组设计
takeIndex 和 putIndex 到达数组末尾后重置为0,实现数组空间复用,效率更高。
3. 锁与条件队列
- 一把
ReentrantLock控制所有操作,保证线程安全 - 两个
Condition精准唤醒:- 入队后唤醒消费者
- 出队后唤醒生产者
比Object.wait/notify效率更高(不会误唤醒不相关线程)。
四、测试:生产者-消费者模型
java
public class BlockingQueueTest {
public static void main(String[] args) {
MyBlockingQueue<Integer> queue = new MyBlockingQueue<>(5);
// 生产者线程:持续入队
new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) {
queue.put(i);
System.out.println("生产者入队:" + i + ",队列大小:" + queue.size());
Thread.sleep(500);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "生产者").start();
// 消费者线程:持续出队
new Thread(() -> {
try {
while (true) {
Integer element = queue.take();
System.out.println("消费者出队:" + element + ",队列大小:" + queue.size());
Thread.sleep(1000);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "消费者").start();
}
}
测试效果
- 队列满时,生产者自动阻塞
- 队列空时,消费者自动阻塞
- 线程安全,无数据错乱
五、Java 官方阻塞队列(生产环境用)
实际开发直接用 JUC 包下的实现类,无需手写:
- ArrayBlockingQueue:数组实现,有界,公平/非公平可选
- LinkedBlockingQueue:链表实现,可选有界/无界
- PriorityBlockingQueue:优先级队列,无界
- SynchronousQueue:不存储元素,直接传递
总结
- 阻塞队列核心:队列满/空时线程阻塞,条件满足时唤醒
- 实现关键:ReentrantLock + Condition + 环形数组
- 手写核心是理解原理,生产环境直接用
java.util.concurrent官方实现 - 必须用
while判断队列状态,防止虚假唤醒