synchronousqueue不存储任务,仅作同步移交通道,任务必须立即被消费,否则触发拒绝策略;常因线程满或提交过快导致拒绝,需配合合理拒绝策略与线程数配置实现反压和降级。

SynchronousQueue 本身不存储任务,它只是一个“传递通道”,生产者线程必须等待消费者线程来接收任务,反之亦然。当它作为 ThreadPoolExecutor 的工作队列时,线程池的行为会变得非常敏感——没有缓冲,任务要么立刻被空闲线程消费,要么触发拒绝策略。
为什么 SynchronousQueue 会频繁触发拒绝策略
因为 SynchronousQueue 不缓存任何任务,只要所有核心线程都在忙,且没有新线程可创建(比如 maximumPoolSize == corePoolSize),新提交的任务就无法入队,直接进入拒绝流程。常见于以下情况:
- 线程池已满(核心线程全忙,且不允许创建更多线程)
- 任务提交速度远高于线程处理速度(哪怕只有瞬时尖峰)
- 使用了默认的
AbortPolicy,抛出RejectedExecutionException
配合拒绝策略的典型用法
关键在于:**把拒绝策略当成“流量整形”或“降级入口”,而不是错误信号**。常用组合如下:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- CallerRunsPolicy:让提交任务的线程自己执行该任务。适合低吞吐、允许调用方短暂阻塞的场景,能自然减缓提交速率
- DiscardPolicy:静默丢弃。适用于任务幂等、丢失可接受(如日志采集、监控埋点)
- DiscardOldestPolicy:丢弃队列头任务(注意:SynchronousQueue 无头可丢,实际效果等同于 DiscardPolicy)
- 自定义策略:例如记录日志 + 发送告警、降级为异步落库、转投消息队列等
正确配置 ThreadPoolExecutor 示例
避免“假死”或无限扩容,需明确边界:
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // corePoolSize:最小常驻线程数
4, // maximumPoolSize:最大线程数(SynchronousQueue 下此值决定并发上限)
60L, // keepAliveTime:空闲线程存活时间(仅对 > corePoolSize 的线程有效)
TimeUnit.SECONDS,
new SynchronousQueue(), // 无缓冲队列
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝时由调用线程执行
);
说明:若提交任务时已有 4 个线程在运行,第 5 个任务就会触发 CallerRunsPolicy,由主线程同步执行——这会拖慢提交方,客观上实现反压。
注意事项与避坑点
- SynchronousQueue 配合
maximumPoolSize过大,可能导致线程数爆炸(尤其未设 keepAliveTime 或设得过大) - 不要搭配
AbortPolicy用于关键业务流,除非你确定能捕获并妥善处理异常 - 监控很重要:关注拒绝任务数、当前活跃线程数、队列 size(始终为 0)可帮你判断是否需要调整线程数或策略
- 如果业务允许延迟,不如换用
LinkedBlockingQueue或带容量的ArrayBlockingQueue,更易控流
不复杂但容易忽略:SynchronousQueue 的本质是“同步移交”,它的价值不在排队,而在强制协作与即时反馈。拒绝策略不是补救措施,而是这个模型中天然的一环。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










