"等待不是浪费时间,而是为了更好的协作!" 🤝
📖 一、什么是阻塞队列?从包子铺说起
1.1 生活中的场景
想象一个包子铺:
没有阻塞队列的世界:
👨🍳 老板疯狂做包子(生产者)
👤 顾客疯狂买包子(消费者)
问题:
- 包子做太快,放不下了!老板手忙脚乱 😰
- 包子卖完了,顾客干等着!老板累死累活 😩
- 两边都不爽!
有了阻塞队列:
👨🍳 老板:做包子 → 放进蒸笼(队列)
如果蒸笼满了,就等等(阻塞)☕
👤 顾客:从蒸笼取包子(队列)
如果蒸笼空了,就等等(阻塞)⏰
蒸笼(阻塞队列):
- 自动控制容量
- 自动协调生产和消费
- 完美平衡!✨
1.2 专业定义
阻塞队列(BlockingQueue) 是 Java 并发包(java.util.concurrent)提供的线程安全队列,具有以下特点:
核心特性:
- ✅ 线程安全:多线程环境下自动同步
- ✅ 自动阻塞:队列满时插入阻塞,队列空时获取阻塞
- ✅ 生产者-消费者模式:天然支持,无需手动加锁
- ⚡ 解决了多线程协作的复杂同步问题
🎯 二、阻塞队列的核心操作
2.1 四种操作方式
BlockingQueue 提供了4种不同的操作方式来处理"队列满"和"队列空"的情况:
| 操作类型 | 抛异常 | 返回特殊值 | 阻塞等待 | 超时退出 |
|---|---|---|---|---|
| 插入 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
| 删除 | remove() | poll() | take() | poll(time, unit) |
| 检查 | element() | peek() | - | - |
2.2 详细说明
🔴 抛异常(Throws Exception)
add(e) - 插入成功返回true,队列满抛IllegalStateException
remove() - 删除成功返回元素,队列空抛NoSuchElementException
element() - 返回队头元素,队列空抛NoSuchElementException
🟡 返回特殊值(Special Value)
offer(e) - 插入成功返回true,失败返回false
poll() - 删除成功返回元素,队列空返回null
peek() - 返回队头元素,队列空返回null
🟢 阻塞等待(Blocks)⭐ 最常用
put(e) - 插入元素,队列满时一直等待(阻塞)
take() - 删除元素,队列空时一直等待(阻塞)
🔵 超时退出(Times Out)
offer(e, timeout, unit) - 插入元素,等待指定时间后超时
poll(timeout, unit) - 删除元素,等待指定时间后超时
2.3 操作对比图
队列状态:[满] [正常] [空]
add(e): ❌抛异常 ✅成功 ✅成功
offer(e): ❌返回false ✅成功 ✅成功
put(e): ⏰等待 ✅成功 ✅成功 ← 阻塞!
remove(): ✅成功 ✅成功 ❌抛异常
poll(): ✅成功 ✅成功 ❌返回null
take(): ✅成功 ✅成功 ⏰等待 ← 阻塞!
🏗️ 三、BlockingQueue的实现类
Java 提供了多种阻塞队列实现,每种有不同的特点:
3.1 ArrayBlockingQueue(有界数组队列)
特点
- 🔹 底层:数组
- 🔹 容量:有界(必须指定容量)
- 🔹 锁:单锁(读写共用一把锁)
- 🔹 公平性:可选(FIFO)
代码示例
import java.util.concurrent.*;
public class ArrayBlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
// 创建容量为3的阻塞队列
BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
// 生产者
new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) {
System.out.println("生产:包子" + i);
queue.put("包子" + i); // 队列满时阻塞
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "生产者").start();
// 消费者
new Thread(() -> {
try {
Thread.sleep(2000); // 延迟启动
while (true) {
String item = queue.take(); // 队列空时阻塞
System.out.println("消费:" + item);
Thread.sleep(1500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "消费者").start();
}
}
输出示例:
生产:包子1
生产:包子2
生产:包子3
生产:包子4 ← 此时队列满,生产者阻塞
消费:包子1 ← 消费者取走一个,生产者继续
生产:包子5
消费:包子2
...
3.2 LinkedBlockingQueue(有界/无界链表队列)
特点
- 🔹 底层:链表
- 🔹 容量:有界或无界(默认Integer.MAX_VALUE)
- 🔹 锁:双锁(读锁putLock + 写锁takeLock)
- 🔹 性能:读写分离,并发性能更好
代码示例
// 无界队列(容量为Integer.MAX_VALUE)
BlockingQueue<Integer> queue1 = new LinkedBlockingQueue<>();
// 有界队列(容量为100)
BlockingQueue<Integer> queue2 = new LinkedBlockingQueue<>(100);
// 示例:生产者-消费者
public class LinkedBlockingQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> queue = new LinkedBlockingQueue<>(5);
// 3个生产者
for (int i = 1; i <= 3; i++) {
final int id = i;
new Thread(() -> {
try {
for (int j = 1; j <= 10; j++) {
String product = "产品-" + id + "-" + j;
queue.put(product);
System.out.println("生产者" + id + " 生产:" + product);
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "生产者" + i).start();
}
// 2个消费者
for (int i = 1; i <= 2; i++) {
final int id = i;
new Thread(() -> {
try {
while (true) {
String product = queue.take();
System.out.println(" 消费者" + id + " 消费:" + product);
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "消费者" + i).start();
}
}
}
3.3 PriorityBlockingQueue(无界优先级队列)
特点
- 🔹 底层:堆(数组实现)
- 🔹 容量:无界(自动扩容)
- 🔹 顺序:优先级顺序(不是FIFO)
- 🔹 线程安全:是
代码示例
class Task implements Comparable<Task> {
String name;
int priority; // 数字越大优先级越高
public Task(String name, int priority) {
this.name = name;
this.priority = priority;
}
@Override
public int compareTo(Task other) {
return other.priority - this.priority; // 降序
}
@Override
public String toString() {
return name + "(P" + priority + ")";
}
}
public class PriorityBlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Task> queue = new PriorityBlockingQueue<>();
// 生产者:乱序添加任务
queue.put(new Task("任务A", 3));
queue.put(new Task("任务B", 10));
queue.put(new Task("任务C", 5));
queue.put(new Task("任务D", 1));
// 消费者:按优先级取出
System.out.println("按优先级执行:");
while (!queue.isEmpty()) {
System.out.println(queue.take());
}
}
}
输出:
按优先级执行:
任务B(P10) ← 最高优先级
任务C(P5)
任务A(P3)
任务D(P1)
3.4 DelayQueue(延迟队列)
特点
- 🔹 底层:PriorityQueue
- 🔹 容量:无界
- 🔹 特性:元素到期后才能取出
- 🔹 应用:定时任务、缓存过期
代码示例
class DelayedTask implements Delayed {
String name;
long executeTime; // 执行时间(毫秒)
public DelayedTask(String name, long delaySeconds) {
this.name = name;
this.executeTime = System.currentTimeMillis() + delaySeconds * 1000;
}
@Override
public long getDelay(TimeUnit unit) {
long diff = executeTime - System.currentTimeMillis();
return unit.convert(diff, TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return Long.compare(this.executeTime, ((DelayedTask) o).executeTime);
}
@Override
public String toString() {
return name;
}
}
public class DelayQueueDemo {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<DelayedTask> queue = new DelayQueue<>();
// 添加延迟任务
queue.put(new DelayedTask("任务1-延迟5秒", 5));
queue.put(new DelayedTask("任务2-延迟2秒", 2));
queue.put(new DelayedTask("任务3-延迟8秒", 8));
System.out.println("开始时间:" + System.currentTimeMillis());
// 取出任务(会阻塞到任务到期)
while (!queue.isEmpty()) {
DelayedTask task = queue.take();
System.out.println(System.currentTimeMillis() + " - 执行:" + task);
}
}
}
输出:
开始时间:1699000000000
1699000002000 - 执行:任务2-延迟2秒 ← 2秒后
1699000005000 - 执行:任务1-延迟5秒 ← 5秒后
1699000008000 - 执行:任务3-延迟8秒 ← 8秒后
3.5 SynchronousQueue(同步队列)
特点
- 🔹 容量:0(不存储元素)
- 🔹 特性:生产者必须等待消费者,一对一交接
- 🔹 应用:线程池(Executors.newCachedThreadPool)
代码示例
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> queue = new SynchronousQueue<>();
// 生产者
new Thread(() -> {
try {
System.out.println("生产者准备放入数据...");
queue.put("数据1"); // 阻塞,直到有消费者取走
System.out.println("生产者成功放入数据!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "生产者").start();
// 消费者(延迟3秒)
new Thread(() -> {
try {
Thread.sleep(3000);
System.out.println("消费者准备取出数据...");
String data = queue.take();
System.out.println("消费者取到:" + data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "消费者").start();
}
}
输出:
生产者准备放入数据...
(等待3秒...)
消费者准备取出数据...
生产者成功放入数据!
消费者取到:数据1
📊 四、实现类对比总结
| 队列类型 | 底层结构 | 容量 | 锁机制 | 排序 | 应用场景 |
|---|---|---|---|---|---|
| ArrayBlockingQueue | 数组 | 有界(必须指定) | 单锁 | FIFO | 资源有限的生产消费 |
| LinkedBlockingQueue | 链表 | 有界/无界 | 双锁 | FIFO | 高并发场景 |
| PriorityBlockingQueue | 堆 | 无界 | 单锁 | 优先级 | 任务调度 |
| DelayQueue | 优先队列 | 无界 | 单锁 | 延迟时间 | 定时任务、缓存过期 |
| SynchronousQueue | 无 | 0 | - | - | 直接交接、线程池 |
🎯 五、经典应用场景
5.1 生产者-消费者模式 🏭
public class ProducerConsumerDemo {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
// 生产者线程
Thread producer = new Thread(() -> {
try {
for (int i = 1; i <= 20; i++) {
queue.put(i);
System.out.println("生产:" + i + ",队列大小:" + queue.size());
Thread.sleep(100);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "生产者");
// 消费者线程
Thread consumer = new Thread(() -> {
try {
while (true) {
Integer item = queue.take();
System.out.println(" 消费:" + item + ",队列大小:" + queue.size());
Thread.sleep(300);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "消费者");
producer.start();
consumer.start();
}
}
5.2 线程池任务队列 💼
// Executors.newFixedThreadPool 内部使用 LinkedBlockingQueue
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
60, // 空闲线程存活时间
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100) // 任务队列
);
// 提交任务
for (int i = 1; i <= 50; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("执行任务:" + taskId);
});
}
5.3 日志异步处理 📝
public class AsyncLogger {
private static final BlockingQueue<String> logQueue =
new LinkedBlockingQueue<>(1000);
static {
// 启动日志消费线程
new Thread(() -> {
while (true) {
try {
String log = logQueue.take();
// 写入文件或数据库
System.out.println("[日志] " + log);
} catch (InterruptedException e) {
break;
}
}
}, "日志线程").start();
}
public static void log(String message) {
try {
logQueue.offer(message, 1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
System.err.println("日志队列满,丢弃日志");
}
}
public static void main(String[] args) {
for (int i = 1; i <= 100; i++) {
log("用户操作 " + i);
}
}
}
5.4 限流器实现 🚦
public class RateLimiter {
private final BlockingQueue<Long> queue;
private final int maxRequests;
private final long timeWindow;
public RateLimiter(int maxRequests, long timeWindowMs) {
this.maxRequests = maxRequests;
this.timeWindow = timeWindowMs;
this.queue = new LinkedBlockingQueue<>(maxRequests);
}
public boolean tryAcquire() {
long now = System.currentTimeMillis();
// 移除过期的时间戳
while (!queue.isEmpty() && now - queue.peek() > timeWindow) {
queue.poll();
}
// 尝试添加新请求
return queue.offer(now);
}
public static void main(String[] args) throws InterruptedException {
RateLimiter limiter = new RateLimiter(5, 1000); // 每秒5个请求
for (int i = 1; i <= 10; i++) {
if (limiter.tryAcquire()) {
System.out.println("请求" + i + ":通过");
} else {
System.out.println("请求" + i + ":被限流");
}
Thread.sleep(150);
}
}
}
🎓 六、经典面试题
面试题1:ArrayBlockingQueue和LinkedBlockingQueue的区别?
答案:
| 特性 | ArrayBlockingQueue | LinkedBlockingQueue |
|---|---|---|
| 底层结构 | 数组 | 链表 |
| 容量 | 必须指定 | 可选(默认无界) |
| 锁机制 | 单锁(读写共用) | 双锁(读写分离) |
| 并发性能 | 较低 | 较高 |
| 内存占用 | 预分配,固定 | 动态分配,节点开销 |
面试题2:put()和offer()的区别?
答案:
put(e):阻塞方法,队列满时会一直等待offer(e):非阻塞,队列满时返回falseoffer(e, timeout, unit):超时阻塞,等待指定时间
面试题3:如何实现一个简单的阻塞队列?
答案:
public class SimpleBlockingQueue<T> {
private final Queue<T> queue = new LinkedList<>();
private final int capacity;
public SimpleBlockingQueue(int capacity) {
this.capacity = capacity;
}
public synchronized void put(T element) throws InterruptedException {
while (queue.size() == capacity) {
wait(); // 队列满,等待
}
queue.add(element);
notifyAll(); // 通知消费者
}
public synchronized T take() throws InterruptedException {
while (queue.isEmpty()) {
wait(); // 队列空,等待
}
T element = queue.remove();
notifyAll(); // 通知生产者
return element;
}
}
面试题4:BlockingQueue在线程池中的作用?
答案:
- 存储待执行的任务
- 解耦任务提交和任务执行
- 控制并发数量(有界队列)
- 实现拒绝策略
🎪 七、趣味小故事
故事:蒸笼的智慧
从前有个包子铺,老板叫张三,是个急性子。
没有阻塞队列的日子:
老板张三每天凌晨3点起床做包子,做得飞快:
- 3点到4点:做了100个包子,堆在柜台上
- 问题来了:柜台只能放50个,其他50个掉地上了!😱
- 老板崩溃:"我这么努力,包子却浪费了!"
下午生意好的时候:
- 顾客疯狂涌入,买光了所有包子
- 老板累得直喘气,手忙脚乱现做
- 顾客不耐烦:"这么慢,不买了!" 😤
有了阻塞队列(蒸笼):
后来,张三买了个智能蒸笼(BlockingQueue):
- 🔹 容量固定50个
- 🔹 满了就自动提醒:"慢点做,我满了!"
- 🔹 空了就提醒:"快做,我空了!"
现在的工作模式:
// 老板(生产者)
while (营业中) {
包子 = 制作包子();
蒸笼.put(包子); // 满了就休息,不浪费
}
// 顾客(消费者)
while (想买包子) {
包子 = 蒸笼.take(); // 空了就等,不着急
}
结果:
- ✅ 老板:按节奏生产,不累不浪费
- ✅ 顾客:总有热包子,不用等太久
- ✅ 包子铺:生意越来越好!🎉
这就是阻塞队列的魔力——让生产者和消费者完美协作!
📚 八、知识点总结
核心要点 ✨
- 定义:线程安全的队列,支持阻塞操作
- 核心方法:put()/take()阻塞,offer()/poll()非阻塞
- 实现类:
- ArrayBlockingQueue:有界数组
- LinkedBlockingQueue:有界/无界链表
- PriorityBlockingQueue:无界优先级
- DelayQueue:延迟队列
- SynchronousQueue:零容量同步
- 应用:生产者-消费者、线程池、异步处理
记忆口诀 🎵
阻塞队列真神奇,
生产消费不着急。
队列满了put会等,
队列空了take阻塞。
Array数组有界限,
Linked链表更灵活。
Priority优先级,
Delay延迟有特色。
线程安全不用锁,
并发编程好帮手!
选择建议 🎯
有界 + 高性能 → ArrayBlockingQueue
高并发 + 吞吐量 → LinkedBlockingQueue
优先级调度 → PriorityBlockingQueue
定时任务 → DelayQueue
直接交接 → SynchronousQueue
🎯 九、练习题
练习1:实现一个有界缓冲区
public class BoundedBuffer<T> {
private final BlockingQueue<T> queue;
public BoundedBuffer(int capacity) {
this.queue = new ArrayBlockingQueue<>(capacity);
}
public void put(T item) throws InterruptedException {
queue.put(item);
}
public T take() throws InterruptedException {
return queue.take();
}
}
练习2:多生产者多消费者
public class MultiProducerConsumer {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(20);
// 3个生产者
for (int i = 1; i <= 3; i++) {
new Producer(queue, i).start();
}
// 2个消费者
for (int i = 1; i <= 2; i++) {
new Consumer(queue, i).start();
}
}
static class Producer extends Thread {
private BlockingQueue<Integer> queue;
private int id;
public Producer(BlockingQueue<Integer> queue, int id) {
this.queue = queue;
this.id = id;
}
@Override
public void run() {
try {
for (int i = 1; i <= 10; i++) {
queue.put(i);
System.out.println("生产者" + id + " 生产:" + i);
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class Consumer extends Thread {
private BlockingQueue<Integer> queue;
private int id;
public Consumer(BlockingQueue<Integer> queue, int id) {
this.queue = queue;
this.id = id;
}
@Override
public void run() {
try {
while (true) {
Integer item = queue.take();
System.out.println(" 消费者" + id + " 消费:" + item);
Thread.sleep(300);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
🌟 十、总结彩蛋
恭喜你!🎉 你已经掌握了阻塞队列这个并发编程的利器!
记住:
- 🔒 阻塞队列 = 线程安全 + 自动阻塞
- 🎯 put()/take() 是最常用的阻塞方法
- 💪 生产者-消费者模式的完美解决方案
- 🚀 不同实现类适用不同场景
最后送你一张图
生产者 消费者
↓ ↑
📦 → [阻塞队列] → 📦
↓
自动协调
✅ 满了等
✅ 空了等
✅ 完美平衡
下次见,继续学习下一个知识点吧! 💪😄
📖 参考资料
- Java官方文档:BlockingQueue
- 《Java并发编程实战》第5章
- 《Java并发编程的艺术》- 阻塞队列
- Doug Lea:《Java并发编程:设计原则与模式》
作者: AI算法导师
最后更新: 2025年11月
难度等级: ⭐⭐⭐⭐ (中高级)
预计学习时间: 3-4小时
💡 温馨提示:阻塞队列是并发编程的基础,理解它的阻塞机制和使用场景非常重要!