线程池饱和需同时满足三条件:线程数达maximumpoolsize、有界队列已满、线程池处于running状态;此时触发拒绝策略,abortpolicy抛异常,callerrunspolicy由调用线程执行,discardpolicy静默丢弃,discardoldestpolicy丢弃最老任务。

线程池饱和不是“满了就拒绝”,而是当任务无法被接纳时,由拒绝策略决定怎么收尾。关键在于三个条件同时成立:线程数已达 maximumPoolSize、工作队列已满(对有界队列而言)、且线程池处于 RUNNING 状态之外或确实无资源可用。此时,rejectedExecution() 被调用,策略开始生效。
四种内置拒绝策略怎么选
每种策略对应不同业务诉求,不能只看“有没有异常”:
-
AbortPolicy(默认):抛出
RejectedExecutionException。适合需要快速感知过载的场景,比如后台批处理、离线计算——失败可重试或告警,不强求任务必达。 - CallerRunsPolicy:让提交任务的线程自己执行该任务。本质是反压机制,能自然降低上游提交速率。适用于关键链路不能丢任务,且调用线程有能力短时承担额外负载(如 Web 请求线程处理简单逻辑)。
- DiscardPolicy:静默丢弃,不抛异常也不记录。适合完全可丢失的任务,例如埋点上报、非核心日志聚合——前提是已有完善的丢弃监控,能及时发现异常丢弃率飙升。
- DiscardOldestPolicy:丢掉队列里最老的一个任务,再尝试提交当前任务。适用于任务时效性极强、旧任务已失效的场景,比如实时行情刷新、状态心跳更新。
拒绝策略真正生效的边界条件
很多人误以为“线程数到 maximum 就触发”,其实必须同时满足:
- 线程池状态为 RUNNING(SHUTDOWN/STOP 状态下新任务一律拒绝);
- 当前活跃线程数 ≥ maximumPoolSize;
- 工作队列 offer() 返回 false(即队列已满,仅对有界队列如
ArrayBlockingQueue有效;若用LinkedBlockingQueue无界队列,几乎永不触发拒绝,除非内存耗尽)。
换句话说,最大承载量 = maximumPoolSize + workQueue.capacity。超过这个总数才会走拒绝逻辑。
自定义拒绝策略的实用要点
写自己的 RejectedExecutionHandler 时,注意三点:
- 避免在拒绝逻辑里做耗时操作(如远程调用、写磁盘),否则会阻塞
execute()调用线程; - 建议记录被拒任务的关键信息:任务类型、提交时间、线程池当前状态(
getActiveCount()、getQueue().size()); - 可结合降级手段,比如将任务转存到 Kafka 或本地文件,后续补偿处理。
生产环境配置建议
拒绝策略不是孤立参数,要和整体线程池设计配合:
- 用有界队列(如
ArrayBlockingQueue),避免无界队列掩盖容量问题; - 设合理的
corePoolSize和maximumPoolSize,使workQueue.capacity能缓冲典型峰值流量; - 拒绝策略搭配监控指标(如拒绝数、队列长度、活跃线程数),设置阈值告警;
- 对重要服务,优先考虑
CallerRunsPolicy或自定义异步落库+告警,而非默认AbortPolicy。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











