高 基础
线程池拒绝策略#
一句话答案#
四种策略:AbortPolicy(抛异常)、CallerRunsPolicy(调用者执行,推荐)、DiscardPolicy(丢弃)、DiscardOldestPolicy(丢最老)。
核心要点
四种策略:
| 策略 | 行为 | 适用场景 |
|---|---|---|
| AbortPolicy(默认) | 抛 RejectedExecutionException | 关键任务,不允许丢失 |
| CallerRunsPolicy | 由提交任务的线程执行 | 降速不丢任务(背压机制) |
| DiscardPolicy | 静默丢弃新任务 | 允许丢失的非关键任务 |
| DiscardOldestPolicy | 丢弃队列最老的任务 | 只关心最新任务 |
生产建议:
- 不要用默认的 AbortPolicy(异常容易被吞)
- 推荐 CallerRunsPolicy:自动限流,不会丢任务
- 自定义策略:记录日志 + 持久化到 MQ/DB + 告警
new ThreadPoolExecutor.CallerRunsPolicy()
// 或自定义:
(r, executor) -> {
log.warn("Task rejected: {}", r);
// 持久化到MQ重试
}java面试回答(2分钟版)
当线程池的工作线程和队列都满了,新提交的任务就会触发拒绝策略。JDK 提供四种内置策略:AbortPolicy 是默认策略,直接抛 RejectedExecutionException,但实际生产中异常容易被吞掉导致任务静默丢失;CallerRunsPolicy 让提交任务的线程自己执行,起到背压限流的效果,不会丢任务,是我最推荐的策略;DiscardPolicy 静默丢弃新任务,DiscardOldestPolicy 丢弃队列中最老的任务。生产环境中我一般不用默认的 AbortPolicy,而是根据业务选择 CallerRunsPolicy 或自定义策略。自定义策略的典型做法是记录告警日志加把被拒绝的任务持久化到消息队列或数据库,后续异步重试,确保关键任务不丢失。拒绝策略是系统保护的最后一道防线,配合合理的线程池参数和监控,才能保障高并发场景下的系统稳定性。
追问与易错
追问方向:
- “CallerRunsPolicy 有什么问题?”→ 提交线程(如 Tomcat 线程)亲自执行任务,会阻塞提交线程导致无法接收新请求,极端情况下 Tomcat 所有线程都在执行被拒绝的任务,接口全部超时
- “你会怎么自定义拒绝策略?”→ 记录告警日志 + 将被拒绝任务持久化到 MQ 或数据库异步重试 + 触发监控告警通知运维扩容,确保关键任务不丢失
- “线程池不当导致过什么线上问题?”→ 经验题,常见案例:使用 Executors.newFixedThreadPool 无界队列导致 OOM、newCachedThreadPool 线程数暴涨耗尽内存、拒绝策略用默认 AbortPolicy 异常被吞任务静默丢失
易错点:
- ❌ 默认 AbortPolicy 就好——异常容易被吞
- ❌ 拒绝策略不重要——是系统保护最后一道防线