🚀 文章提示
你将在这篇文章中收获:
阻塞队列的核心特性:队列空/满时的阻塞机制
四种操作方式对比:抛异常、返回特殊值、永久阻塞、超时阻塞
SynchronousQueue的独特设计:同步队列的生产者-消费者强耦合
代码实战:通过20+个案例彻底掌握阻塞队列的API
目录
BlockingQueue BlockingQueue 不是新的东西
🌟 前言
在多线程编程中,线程间的高效通信是保证程序正确性的关键。想象一个外卖配送场景:骑手(生产者)不断接单,商家(消费者)按顺序处理订单。如果订单爆单时骑手还在盲目塞单,或者无单时商家不停空跑,都会造成系统崩溃。
这正是 阻塞队列(BlockingQueue) 要解决的核心问题!它像一座智能缓冲桥梁,让生产者和消费者线程安全、高效地协作。本文将用最通俗的案例+代码,带你彻底掌握这个并发编程利器。
📚 一、阻塞队列的本质
阻塞队列 = 普通队列 + 线程协调机制
-
队列空时:消费者线程自动挂起,直到有数据
-
队列满时:生产者线程自动挂起,直到有空位
-
自带线程唤醒机制:无需手动wait/notify
BlockingQueue BlockingQueue 不是新的东西

为什么需要阻塞队列?
什么情况下我们会使用 阻塞队列:多线程并发处理,线程池!
场景 | 传统实现痛点 | 阻塞队列解决方案 |
---|---|---|
生产者速度 > 消费者 | 队列爆满导致数据丢失 | 自动阻塞生产者线程 |
消费者速度 > 生产者 | 空轮询消耗CPU资源 | 自动挂起消费者线程 |
流量突增 | 需要手动扩容/限流 | 内置容量控制机制 |
🔧 二、四大核心操作API对比
以ArrayBlockingQueue为例,演示不同操作方式:
方式
|
抛出异常
|
有返回值,不抛出异常
|
阻塞 等待
|
超时等待
|
---|---|---|---|---|
添加
| add | offer() | put() | offer(,,) |
移除
| remove | poll() | take() | poll(,) |
检测队首元素
| element | peek | - |
1. 抛异常型(新手慎用)
public static void testThrowException() {
BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
System.out.println(queue.add("A")); // true
System.out.println(queue.add("B")); // true
System.out.println(queue.add("C")); // true
queue.add("D"); // 抛出IllegalStateException
System.out.println(queue.remove()); // A
System.out.println(queue.remove()); // B
System.out.println(queue.remove()); // C
queue.remove(); // 抛出NoSuchElementException
}
2. 返回特殊值型(推荐基础使用)
public static void testReturnSpecial() {
BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
System.out.println(queue.offer("A")); // true
System.out.println(queue.offer("B")); // true
System.out.println(queue.offer("C")); // true
System.out.println(queue.offer("D")); // false (静默失败)
System.out.println(queue.poll()); // A
System.out.println(queue.poll()); // B
System.out.println(queue.poll()); // C
System.out.println(queue.poll()); // null
}
3. 永久阻塞型(线程池底层使用)
public static void testForeverBlock() throws InterruptedException {
BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
queue.put("A"); // 成功
queue.put("B"); // 成功
queue.put("C"); // 成功
queue.put("D"); // 线程在此处永久挂起!!
System.out.println(queue.take()); // A
System.out.println(queue.take()); // B
System.out.println(queue.take()); // C
System.out.println(queue.take()); // 永久等待新元素...
}
4. 超时阻塞型(高并发推荐)
public static void testTimeoutBlock() throws InterruptedException {
BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
System.out.println(queue.offer("A", 2, TimeUnit.SECONDS)); // true
System.out.println(queue.offer("B", 2, TimeUnit.SECONDS)); // true
System.out.println(queue.offer("C", 2, TimeUnit.SECONDS)); // true
System.out.println(queue.offer("D", 2, TimeUnit.SECONDS)); // 2秒后返回false
System.out.println(queue.poll(2, TimeUnit.SECONDS)); // A
System.out.println(queue.poll(2, TimeUnit.SECONDS)); // B
System.out.println(queue.poll(2, TimeUnit.SECONDS)); // C
System.out.println(queue.poll(2, TimeUnit.SECONDS)); // 等待2秒后返回null
}
完整代码:
package JUC.bq;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
test1();
}
/**
* 抛出异常
*/
public static void test1() {
// 队列的大小创建阻塞队列
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);//队列大小
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
// IllegalStateException: Queue full 抛出异常!
// System.out.println(blockingQueue.add("d"));
System.out.println("===============");
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
// java.util.NoSuchElementException 抛出异常!
// System.out.println(blockingQueue.remove());
}
/**
* 有返回值,没有异常
*/
public static void test2() {
// 队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
// System.out.println(blockingQueue.offer("d")); // false 不抛出异常!
System.out.println("============================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll()); // null 不抛出异常!
}
/**
* 等待,阻塞(一直阻塞)
*/
public static void test3() throws InterruptedException {
// 队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
// 一直阻塞
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
// blockingQueue.put("d"); // 队列没有位置了,一直阻塞
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take()); // 没有这个元素,一直阻塞
}
/**
* 等待,阻塞(等待超时)
*/
public static void test4() throws InterruptedException {
// 队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
// blockingQueue.offer("d",2,TimeUnit.SECONDS); // 等待超过2秒就退出
System.out.println("===============");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
blockingQueue.poll(2, TimeUnit.SECONDS); // 等待超过2秒就退出
}
}
🎯 三、阻塞队列分类及选型
队列类型 | 特性 | 适用场景 |
---|---|---|
ArrayBlockingQueue | 数组实现,固定容量 | 已知固定并发量的生产消费模型 |
LinkedBlockingQueue | 链表实现,可选容量(默认Integer.MAX) | 高吞吐量场景 |
SynchronousQueue | 无缓冲队列,直接传递 | 线程间精准握手(AQS底层实现) |
PriorityBlockingQueue | 优先级排序 | 任务优先级调度系统 |
DelayQueue | 延迟执行队列 | 定时任务调度 |
💡 四、SynchronousQueue深度解析
1.同步队列的三大特征:
-
容量始终为0,不能提前存储元素
-
put操作必须等待take操作,反之亦然
-
适合传递性任务(线程间直接交接数据)
-
进去一个元素,必须等待取出来之后,才能再往里面放一个元素!
2.代码演示:订单即时处理系统
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> syncQueue = new SynchronousQueue<>();
// 厨师线程(生产者)
new Thread(() -> {
try {
System.out.println("厨师接到订单:汉堡");
syncQueue.put("汉堡"); // 等待骑手取餐
System.out.println("厨师接到订单:薯条");
syncQueue.put("薯条");
System.out.println("厨师接到订单:可乐");
syncQueue.put("可乐");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "厨师线程").start();
// 骑手线程(消费者)
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
System.out.println("骑手取到:" + syncQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println("骑手取到:" + syncQueue.take());
TimeUnit.SECONDS.sleep(1);
System.out.println("骑手取到:" + syncQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "骑手线程").start();
}
}
代码示例:
package JUC.bq;
import java.sql.Time;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
* 同步队列
* 和其他的BlockingQueue 不一样, SynchronousQueue 不存储元素
* put了一个元素,必须从里面先take取出来,否则不能在put进去值!
*/
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>(); // 同步队列
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+" put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName()+" put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName()+" put 3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
输出结果分析:
厨师接到订单:汉堡 // 厨师开始制作汉堡 (等待3秒) 骑手取到:汉堡 // 骑手取走汉堡 厨师接到订单:薯条 // 厨师立即开始做薯条 (等待2秒) 骑手取到:薯条 // 取走薯条 厨师接到订单:可乐 // 开始做可乐 (等待1秒) 骑手取到:可乐 // 最后取可乐
🚨 五、避坑指南
-
容量选择:LinkedBlockingQueue默认长度是Integer.MAX_VALUE(约21亿),可能引发OOM
-
方法混淆:poll()与take()要根据是否需要永久等待来选择
-
公平性设置:ArrayBlockingQueue构造函数可设置公平锁,避免线程饥饿
-
异常处理:永远不要忽略offer/poll返回的boolean结果
🌈 六、实战应用场景
线程池任务队列
// ThreadPoolExecutor的第五个参数就是BlockingQueue
ExecutorService pool = new ThreadPoolExecutor(
2, // 核心线程数
5, // 最大线程数
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100) // 任务队列
);
生产者-消费者日志系统
// 日志缓存队列
BlockingQueue<LogMessage> logQueue = new ArrayBlockingQueue<>(500);
// 生产者(多个业务线程)
public void log(String message) {
logQueue.offer(new LogMessage(message, System.currentTimeMillis()));
}
// 消费者(专用日志线程)
class LogConsumer implements Runnable {
@Override
public void run() {
while(true) {
try {
LogMessage log = logQueue.take();
writeToDisk(log); // 实际写入操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
🎓 总结与提升
阻塞队列的本质是线程间的通信工具,它通过智能的阻塞/唤醒机制,完美解决了生产者和消费者的速度匹配问题。通过本文的4种API对比+5种队列类型解析+真实场景案例,你应该已经掌握了:
基础选择原则
-
需要快速失败选
offer()/poll()
-
需要资源保护选
put()/take()
-
精准控制用超时API
进阶使用技巧
-
SynchronousQueue适合线程间直接传递数据的场景(如Executors.newCachedThreadPool)
-
PriorityBlockingQueue实现VIP客户优先处理
-
使用DelayQueue做定时任务调度(比Timer更安全)
性能优化方向
-
监控队列的
remainingCapacity()
提前预警 -
使用
drainTo()
方法批量处理元素提升吞吐量 -
结合Guava的Queues工具类进行高级操作
最后思考:当使用put()方法阻塞时,如何优雅地终止线程?欢迎在评论区分享你的方案!