什么是阻塞队列?
阻塞队列是一种特殊的队列,它在数据结构的基础上附加了两个额外的操作特性:
- 阻塞插入:当队列已满时,尝试向队列中插入元素的线程会被阻塞,直到队列中有空闲位置。
- 阻塞移除:当队列为空时,尝试从队列中获取元素的线程会被阻塞,直到队列中有新的元素被加入。
简单来说,阻塞队列是一个线程安全的、支持阻塞等待的生产者 - 消费者模型的核心容器。
阻塞队列的实现原理
阻塞队列的实现原理主要依赖于 锁(Lock) 和 条件变量(Condition)。在 Java 中,这通常通过 ReentrantLock 和 Condition 来实现。
我们以一个简单的有界数组阻塞队列为例,剖析其核心原理:
核心组件
- 一个队列:通常用数组或链表实现,用于存储元素。
- 一把锁:一个
ReentrantLock,用于保证所有操作的线程安全性。 - 两个条件变量:
notEmpty:一个与锁绑定的条件,用于表示'队列非空'。当消费者因队列为空而等待时,会挂在这个条件上。当生产者放入一个新元素后,会唤醒挂在这个条件上的线程。notFull:一个与锁绑定的条件,用于表示'队列未满'。当生产者因队列已满而等待时,会挂在这个条件上。当消费者取走一个元素后,会唤醒挂在这个条件上的线程。
核心方法原理
put(E e) 方法(阻塞插入)
- 获取锁。
- while (队列已满):
- 调用
notFull.await()释放锁并进入等待状态。 - 当被其他线程唤醒并重新获得锁后,再次检查队列是否已满(防止虚假唤醒)。
- 调用
- 将元素
e入队。 - 调用
notEmpty.signal()或notEmpty.signalAll(),唤醒一个或所有正在notEmpty上等待的消费者线程。 - 释放锁。
take() 方法(阻塞移除)
- 获取锁。
- while (队列为空):
- 调用
notEmpty.await()释放锁并进入等待状态。 - 当被其他线程唤醒并重新获得锁后,再次检查队列是否为空。
- 将队首元素出队。
- 调用
notFull.signal()或notFull.signalAll(),唤醒一个或所有正在notFull上等待的生产者线程。 - 释放锁。
关键点总结:
- 线程安全:所有对队列结构的修改都在锁的保护下进行。
- 高效等待/通知:使用
Condition的await()和signal()代替传统的Object.wait()和Object.notify(),可以更精确地控制等待和唤醒的线程类型(生产者或消费者),避免了'惊群效应'。


