文章目录
📙 作者: 编程技术圈(哇哥面试陪跑)
👉 欢迎关注、分享、评论
✔️ 持续分享更多干货内容
🌐🌏🌎➕tcmeta
零、引入

“订单又丢了!用户付了钱,系统没下单,客服电话被打爆了!” 王二的哀嚎响彻技术部,他负责的订单系统上线后,要么因为任务堆积 OOM 卡死,要么因为队列满了直接拒绝任务,光用户投诉就攒了上百条。领导把投诉单拍在他桌上,指节咔咔响:“再搞不定,这个月绩效清零,还得赔用户损失!”
王二的代码里,线程池要么用无界的 LinkedBlockingQueue,高峰期任务堆成山,内存直接炸了;换成有界 ArrayBlockingQueue,又因为 RejectedExecutionException 丢单 —— 用户付了钱,系统直接提示 “繁忙”,订单凭空消失。隔壁哇哥端着泡满枸杞的保温杯过来,扫了眼代码就乐了:“你这是没搞懂阻塞队列的核心 —— 普通队列满了就拒,空了就返回 null;阻塞队列满了会等,空了也会等,正好解决你的丢单和 OOM 问题。今天把这事儿搞懂,不仅能救你的绩效,下次面试被问阻塞队列,你能把面试官说懵。”
点赞 + 关注,跟着哇哥和王二,用食堂打饭的例子搞懂阻塞队列,代码直接抄,再也不怕订单丢了、服务器炸了!
一、王二的 “丢单代码”:线程池队列的两个致命坑
王二的订单处理逻辑很简单:用户下单请求过来,线程池处理 “查库存→扣余额→写订单” 的流程。单测时顺风顺水,一到高峰期就出问题 —— 要么 OOM,要么丢单。
🥰 王二的 “自杀式” 订单代码

package cn.tcmeta.blockingqueue;
import java.util.concurrent.*;
import static java.lang.Thread.sleep;
/**
* @author: laoren
* @description: “自杀式” 订单代码
* @version: 1.0.0
*/
// 订单系统——丢单+OOM版
public class OrderSystemBug {
// 线程池配置:核心5线程,最大10线程,有界队列100
static ExecutorService pool = new ThreadPoolExecutor(
5, 10,
60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), // 有界队列
new ThreadPoolExecutor.AbortPolicy() // 队列满了直接拒绝任务
);
// 模拟下单任务
record OrderTask(int orderId) implements Runnable {
@Override
public void run() {
// 模拟订单处理耗时(查库存、扣余额、写日志)
try {
sleep(100);
System.out.println("处理订单:" + orderId + ",线程:" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 模拟高峰期:1000个下单请求
for (int i = 1; i <= 1000; i++) {
try {
pool.submit(new OrderTask(i));
} catch (RejectedExecutionException e) {
// 队列满了,任务被拒绝——订单丢了!
System.out.println("订单" + i + "处理失败:系统繁忙(用户付了钱没下单!)");
}
}
pool.shutdown();
}
}
执行结果:
订单111处理失败:系统繁忙(用户付了钱没下单!)
订单112处理失败:系统繁忙(用户付了钱没下单!)
订单113处理失败:系统繁忙(用户付了钱没下单!)
订单114处理失败:系统繁忙(用户付了钱没下单!)
订单115处理失败:系统繁忙(用户付了钱没下单!)
订单116处理失败:系统繁忙(用户付了钱没下单!)
订单117处理失败:系统繁忙(用户付了钱没下单!)
订单118处理失败:系统繁忙(用户付了钱没下单!)
订单119处理失败:系统繁忙(用户付了钱没下单!)
订单120处理失败:系统繁忙(用户付了钱没下单!)
订单121处理失败:系统繁忙(用户付了钱没下单!)
订单122处理失败:系统繁忙(用户付了钱没下单!)
订单123处理失败:系统繁忙(用户付了钱没下单!)
订单124处理失败:系统繁忙(用户付了钱没下单!)
订单125处理失败:系统繁忙(用户付了钱没下单!)
订单126处理失败:系统繁忙(用户付了钱没下单!)
订单127处理失败:系统繁忙(用户付了钱没下单!)
运行结果:用户钱付了,订单没了
控制台满屏 “订单 XXX 处理失败”,用户在 APP 上显示 “支付成功”,但商家后台没订单,客服电话被打爆 —— 这就是典型的 “队列满了直接拒绝” 导致的丢单问题;如果把队列换成无界的 LinkedBlockingQueue,又会因为任务无限堆积导致 OOM,服务器直接卡死。
二、用食堂打饭,讲透阻塞队列的核心逻辑
“你把线程池的队列想成食堂的打饭窗口,” 哇哥拿王二的午饭举例子,“普通队列就像窗口贴了‘满 3 人就拒’,来第 4 个人直接赶跑(丢单);无界队列像窗口无限排队,人堆到食堂门口,挤爆大门(OOM)。而阻塞队列是‘人性化窗口’:
- 窗口满了(队列满),新来的人不跑,站在后面等(线程阻塞),直到有人打完饭有空位;
- 窗口没人(队列空),打饭阿姨也不闲着,等着来人(消费线程阻塞),直到有新的人来打饭。”
✔️ 阻塞队列的核心特性(人话版)

“阻塞队列的 put () 和 take () 是核心 —— 这两个方法会‘等’,而不是‘拒’或‘抛异常’,完美解决你的丢单和 OOM 问题。”
三、第一招:用 ArrayBlockingQueue 改造线程池,丢单 + OOM 全解决
哇哥教王二改了两行代码:把线程池的拒绝策略换成 CallerRunsPolicy(队列满了提交线程自己执行,不丢单),队列用有界阻塞队列控制容量(防 OOM)。
🥰 修复后的订单系统(无丢单 + 无 OOM)

package cn.tcmeta.blockingqueue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import static java.lang.Thread.sleep;
/**
* @author: laoren
* @description: // 订单系统——阻塞队列修复版
* @version: 1.0.0
*/
public class OrderSystemFixed {
// 核心改造:阻塞队列+合理拒绝策略
static ExecutorService pool = new ThreadPoolExecutor(
5,
10, // 核心5线程,最大10线程
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), // 有界阻塞队列,控制容量防OOM
// 拒绝策略:队列满了,提交任务的线程自己执行,不丢单
new ThreadPoolExecutor.CallerRunsPolicy()
);
record OrderTask(int orderId) implements Runnable {
@Override
public void run() {
try {
sleep(100); // 模拟业务耗时
System.out.println("处理订单:" + orderId + ",线程:" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 模拟1000个下单请求
for (int i = 1; i <= 1000; i++) {
pool.submit(new OrderTask(i)); // 不会丢单,队列满了提交线程自己执行
}
// 等待所有任务处理完成
pool.shutdown();
try {
pool.awaitTermination(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("所有订单处理完成,无丢单!");
}
}
执行结果:
处理订单:111,线程:main
处理订单:3,线程:pool-1-thread-3
处理订单:107,线程:pool-1-thread-7
处理订单:4,线程:pool-1-thread-4
处理订单:5,线程:pool-1-thread-5
处理订单:106,线程:pool-1-thread-6
处理订单:2,线程:pool-1-thread-2
处理订单:1,线程:pool-1-thread-1
处理订单:110,线程:pool-1-thread-10
处理订单:108,线程:pool-1-thread-8
处理订单:109,线程:pool-1-thread-9
处理订单:11,线程:pool-1-thread-2
运行效果:订单全处理,内存稳如狗
王二跑起来发现,控制台再也没有 “订单处理失败” 的日志,所有 1000 个订单都被处理了,内存占用只有之前的 1/3—— 阻塞队列既限制了任务堆积(防 OOM),又通过阻塞和合理的拒绝策略避免了丢单。
📢 阻塞队列基础用法(新手必看)
为了让王二彻底理解,哇哥写了段极简代码,展示 ArrayBlockingQueue 的 put () 和 take ():
package cn.tcmeta.blockingqueue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* @author: laoren
* @description: // 阻塞队列基础:ArrayBlockingQueue
* @version: 1.0.0
*/
public class BlockingQueueBasic {
public static void main(String[] args) throws InterruptedException {
// 创建有界阻塞队列:容量3
ArrayBlockingQueue<String> orderQueue = new ArrayBlockingQueue<>(3);
// 生产者线程:放订单,满了就等
new Thread(() -> {
try {
orderQueue.put("订单1");
orderQueue.put("订单2");
orderQueue.put("订单3");
System.out.println("生产者:队列满了,等位置放订单4...");
orderQueue.put("订单4"); // 阻塞,直到消费者取走订单
System.out.println("生产者:订单4放入成功!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "生产者").start();
// 等2秒,让生产者放满3个订单
Thread.sleep(2000);
// 消费者线程:取订单,空了就等
new Thread(() -> {
try {
System.out.println("消费者:取走" + orderQueue.take());
System.out.println("消费者:取走" + orderQueue.take());
// 取走2个,队列剩1个,生产者的订单4可以放入了
TimeUnit.SECONDS.sleep(1);
System.out.println("消费者:取走" + orderQueue.take());
System.out.println("消费者:队列空了,等新订单...");
orderQueue.take(); // 阻塞,直到有新订单
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "消费者").start();
}
}
执行结果:
生产者:队列满了,等位置放订单4...
生产者:订单4放入成功!
消费者:取走订单1
消费者:取走订单2
消费者:取走订单3
消费者:队列空了,等新订单...
运行结果清晰展示了 “满了等、空了等” 的核心逻辑,王二一眼就懂了。
四、阻塞队列的核心优势(王二记满 3 页笔记)
王二掏出小本本,把哇哥的话记成 “傻瓜式总结”:
- 防 OOM:有界阻塞队列限制容量,任务不会无限堆积,内存稳如老狗;
- 防丢单:put () 阻塞等待,配合 CallerRunsPolicy 拒绝策略,任务不会被直接拒绝;
- 解耦:生产者(下单请求)和消费者(订单处理)解耦,不用直接交互;
- 简单:JDK 自带,不用自己写 wait/notify 的阻塞逻辑,避免死锁。
💯 哇哥的血泪彩蛋
“我刚工作时,自己用 wait/notify 写阻塞逻辑,” 哇哥捂脸,“结果写死锁了,把测试库搞崩了,被 DBA 追着骂 —— 后来用阻塞队列,再也没翻过车,现成的轮子不香吗?”
‼️ 下一篇预告
王二用 ArrayBlockingQueue 解决了丢单问题,但新问题又来了:“秒杀系统用这个队列,订单堆积在队列里,响应慢成龟速!” 哇哥说这是 “队列选型错了”—— 下一篇我们扒透:
- SynchronousQueue:无存储队列,秒杀系统 QPS 翻倍的秘密;
- PriorityBlockingQueue:VIP 订单优先处理的玩法;
- DelayQueue:订单超时未支付自动取消的实现;





被折叠的 条评论
为什么被折叠?



