文章目录
📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌
📙 作者: 编程技术圈(哇哥面试陪跑)
👉 欢迎关注、分享、评论
✔️ 持续分享更多干货内容
🌐🌏🌎➕tcmeta, 欢迎沟通交流
📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌
零、引入
“线程池又卡爆了!任务堆积到上万,CPU 干到 100%,用户下单直接超时!” 王二拍着键盘哀嚎,他给秒杀系统写的线程池,用 LinkedBlockingQueue 当任务队列,结果一到高峰期就崩,监控里的任务队列长度像坐了火箭。
隔壁哇哥端着枸杞茶晃过来,扫了眼代码乐了:“你这是拿‘快递柜’装实时订单啊!换成 SynchronousQueue,保证任务秒处理,性能直接起飞 —— 今天我把这玩意掰开揉碎讲,下次再用错,我把你枸杞水换成白开水!”

点赞 + 关注,跟着哇哥和王二,用 “快递交接” 的逻辑吃透 SynchronousQueue,不仅会用,还能讲透底层,面试时直接碾压面试官!
一、王二的坑:用 “快递柜” 装实时订单,任务堆成山

王二的秒杀线程池代码长这样,看着规规矩矩,却藏着致命问题:
package cn.tcmeta.queues;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author: laoren
* @description: // 王二的坑代码:秒杀线程池用LinkedBlockingQueue
* @version: 1.0.0
*/
public class BadThreadPoolSample {
// 核心线程数5,最大线程数10,队列容量1000
private static final ThreadPoolExecutor POOL = new ThreadPoolExecutor(
5,
10,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000) // 快递柜式队列,能存1000个任务
);
// 处理秒杀订单任务
public static void handleSeckillOrder(String orderId) {
POOL.submit(() -> {
try {
// 模拟处理订单(耗时10ms)
TimeUnit.MILLISECONDS.sleep(10);
System.out.println(Thread.currentThread().getName() + "处理订单:" + orderId);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
static void main() throws InterruptedException {
long start = System.currentTimeMillis();
// 模拟秒杀:10000个订单请求
for (int i = 0; i < 10000; i++) {
String orderId = "SECKILL_" + i;
handleSeckillOrder(orderId);
}
POOL.shutdown();
POOL.awaitTermination(5, TimeUnit.MINUTES);
long end = System.currentTimeMillis();
System.out.println("总耗时:" + (end - start) + "ms");
}
}
- 运行结果:惨不忍睹
pool-1-thread-10处理订单:SECKILL_867
pool-1-thread-9处理订单:SECKILL_871
pool-1-thread-5处理订单:SECKILL_874
pool-1-thread-1处理订单:SECKILL_872
pool-1-thread-4处理订单:SECKILL_873
pool-1-thread-7处理订单:SECKILL_875
pool-1-thread-8处理订单:SECKILL_879
pool-1-thread-2处理订单:SECKILL_878
pool-1-thread-3处理订单:SECKILL_876
pool-1-thread-6处理订单:SECKILL_877
pool-1-thread-10处理订单:SECKILL_880
pool-1-thread-1处理订单:SECKILL_883
pool-1-thread-4处理订单:SECKILL_884
王二挠头:“队列容量设 1000 够大了啊,为啥还卡?”
哇哥把枸杞茶往桌上一墩:“你这是没搞懂队列的本质!LinkedBlockingQueue 是‘快递柜’,任务来了先存起来,等线程有空了再取;
但秒杀订单是‘实时快递’,必须当面交接,不能存 ——SynchronousQueue 就是‘当面交接’的快递员,生产任务的线程必须等消费线程来取,消费线程也必须等生产线程来给,中间不存任何东西,任务到了就直接处理,哪来的堆积?”
二、用 “快递交接” 讲透 SynchronousQueue 的核心
哇哥拉过白板,画了两张图,王二一下就懂了:普通阻塞队列是 “快递柜”,SynchronousQueue 是 “当面交接”,核心区别天差地别!
👉 画图对比:普通队列 vs SynchronousQueue
- SynchronousQueue

- 普通队列

📒 SynchronousQueue 的核心特性(王二记小本本)

- 零容量:这是最核心的特点!SynchronousQueue 里永远存不下任何任务,就像快递员手里不囤包裹,只负责当面交接;
- 生产消费互等:生产者线程调用 put (),必须等消费者线程调用 take () 才能返回;反之,消费者调用 take (),也必须等生产者调用 put ()—— 少一步都不行;
- 公平 / 非公平模式:默认非公平(效率高),公平模式下按 FIFO 顺序交接,避免线程饥饿;
- 线程池绝配:配合线程池使用时,核心线程数设小,最大线程数设大(比如 Integer.MAX_VALUE),因为任务不缓存,来了就需要线程处理,没线程就新建,处理完就销毁。
👉 “简单说,SynchronousQueue 就是‘无缓冲的交接器’,” 哇哥总结,“适合那些‘任务必须即时处理,不能等’的场景,比如秒杀、实时交易、高频 RPC 调用 —— 这些场景堆任务就是找死!”
三、改造代码:SynchronousQueue 让线程池性能暴涨 10 倍

哇哥手把手教王二改代码,只换了队列类型,再加了点线程池参数优化,结果总耗时从 8920ms 降到 850ms,性能直接涨了 10 倍!
✔️ 优化后的代码(SynchronousQueue 救场)

package cn.tcmeta.queues;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author: laoren
* @description: // 优化版:秒杀线程池用SynchronousQueue
* @version: 1.0.0
*/
public class GoodThreadPoolSample {
// 核心线程数5,最大线程数设为Integer.MAX_VALUE(无界),用SynchronousQueue
private static final ThreadPoolExecutor POOL = new ThreadPoolExecutor(
5,
Integer.MAX_VALUE, // 任务不缓存,需要时新建线程
60L,
TimeUnit.SECONDS,
new SynchronousQueue<>() // 当面交接队列,零容量
);
// 处理秒杀订单任务(逻辑和之前一样)
public static void handleSeckillOrder(String orderId) {
POOL.submit(() -> {
try {
TimeUnit.MILLISECONDS.sleep(10);
System.out.println(Thread.currentThread().getName() + "处理订单:" + orderId);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
static void main() throws InterruptedException {
long start = System.currentTimeMillis();
// 同样模拟10000个秒杀订单
for (int i = 0; i < 10000; i++) {
String orderId = "SECKILL_" + i;
handleSeckillOrder(orderId);
}
POOL.shutdown();
POOL.awaitTermination(5, TimeUnit.MINUTES);
long end = System.currentTimeMillis();
System.out.println("总耗时:" + (end - start) + "ms");
}
}
- 执行结果
...
pool-1-thread-1874处理订单:SECKILL_9542
pool-1-thread-1872处理订单:SECKILL_9535
pool-1-thread-1870处理订单:SECKILL_9527
pool-1-thread-1886处理订单:SECKILL_9630
pool-1-thread-1871处理订单:SECKILL_9530
总耗时:426ms
王二眼睛瞪得像铜铃:“就换了个队列,耗时直接砍到 1/10?这 SynchronousQueue 也太顶了吧!”

“这还只是基础操作,” 哇哥挑眉,“上次给某电商做秒杀优化,把线程池队列换成 SynchronousQueue 后,QPS 从 5 万涨到 50 万,延迟从 200ms 降到 20ms—— 核心就是任务不堆积,即时处理!”
四、SynchronousQueue 的基本使用(手把手教,直接抄)

光改线程池不够,哇哥还教王二手写 SynchronousQueue 的生产者消费者示例,让他彻底掌握底层用法。
👉 示例:SynchronousQueue 的生产者消费者(当面交接任务)
package cn.tcmeta.queues;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
* @author: laoren
* @description: SynchronousQueue基本使用:生产者消费者当面交接
* @version: 1.0.0
*/
public class SynchronousQueueBasicSample {
static void main() {
// 创建SynchronousQueue,公平模式(按顺序交接)
SynchronousQueue<String> queue = new SynchronousQueue<>(true);
// 生产者线程(寄件人):生产任务,必须等消费者来取
new Thread(() -> {
try {
String task1 = "秒杀订单_001";
System.out.println(Thread.currentThread().getName() + "准备生产任务:" + task1);
// put()方法:阻塞,直到有消费者取走任务
queue.put(task1);
System.out.println(Thread.currentThread().getName() + "任务" + task1 + "已交接");
String task2 = "秒杀订单_002";
System.out.println(Thread.currentThread().getName() + "准备生产任务:" + task2);
queue.put(task2);
System.out.println(Thread.currentThread().getName() + "任务" + task2 + "已交接");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "生产者线程").start();
// 消费者线程(收件人):取任务,必须等生产者来给
new Thread(() -> {
try {
// 先睡1秒,模拟消费者晚到
TimeUnit.SECONDS.sleep(1);
String task = queue.take(); // take()方法:阻塞,直到有生产者给任务
System.out.println(Thread.currentThread().getName() + "收到任务:" + task + ",开始处理");
TimeUnit.SECONDS.sleep(1);
task = queue.take();
System.out.println(Thread.currentThread().getName() + "收到任务:" + task + ",开始处理");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "消费者线程").start();
}
}
- 运行结果:当面交接,无缓存
生产者线程准备生产任务:秒杀订单_001
消费者线程收到任务:秒杀订单_001,开始处理
生产者线程任务秒杀订单_001已交接
生产者线程准备生产任务:秒杀订单_002
生产者线程任务秒杀订单_002已交接
消费者线程收到任务:秒杀订单_002,开始处理
➡️ 核心要点(哇哥划重点)
- put/take 是阻塞方法:put () 生产任务,没消费者就一直等;take () 消费任务,没生产者也一直等;
- offer/poll 是非阻塞方法:offer () 生产任务,没消费者就返回 false;poll () 消费任务,没生产者就返回 null,还能设置超时时间(比如 offer (1, TimeUnit.SECONDS));
- 公平模式 vs 非公平模式:公平模式(new SynchronousQueue (true))按 FIFO 顺序交接,适合对顺序有要求的场景;非公平模式(默认)效率更高,适合追求性能的场景。
五、什么时候用 SynchronousQueue?(附 Mermaid 决策图)
为了让王二不再用错,哇哥画了一张 “场景决策图”,照着选绝对不会踩坑!
‼️ 选择策略

🔥 避坑指南(90% 的人踩过的 3 个坑)
❌ 坑 1:用 SynchronousQueue 却设固定线程数
// 错误:核心线程数=最大线程数=5,任务来了没线程处理,直接拒绝
new ThreadPoolExecutor(5,5,60L,TimeUnit.SECONDS,new SynchronousQueue<>());
正确做法:最大线程数设大(比如 Integer.MAX_VALUE),让线程池能新建线程处理任务。
❌ 坑 2:用 SynchronousQueue 处理非实时任务
比如批量处理日志、数据导出,这些任务允许缓存,用 SynchronousQueue 会导致线程数暴涨,CPU 飙升 —— 老老实实用 LinkedBlockingQueue。
❌ 坑 3:没配置拒绝策略
SynchronousQueue 配合无界线程数时,极端情况下线程数会耗尽系统资源,必须配置拒绝策略:
// 正确:配置拒绝策略,任务太多时直接拒绝并记录日志
new ThreadPoolExecutor(
5,
Integer.MAX_VALUE,
60L,
TimeUnit.SECONDS,
new SynchronousQueue<>(),
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝时让调用线程自己处理
);
六、总结:SynchronousQueue 核心心法(王二编顺口溜)
📒 面试必背
王二把核心知识点编成顺口溜,贴在显示器上:
- 同步队列零容量,生产消费面对面;
- 实时任务用它爽,任务堆积靠边站;
- 线程池配无界数,即时处理不拖延;
- 普通任务别乱用,快递柜才是好伙伴。
📢 哇哥的面试彩蛋
“面试时被问 SynchronousQueue,你就按这个逻辑说,” 哇哥传授技巧,“先讲‘快递柜 vs 当面交接’的比喻,再画 Mermaid 对比图,然后讲线程池优化的实战案例,最后说避坑指南 —— 这套组合拳打出去,面试官绝对觉得你是并发高手!我当年面阿里,就靠这个知识点拿了高薪 offer。”



1242

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



