线程池有哪些拒绝策略?
未标记
考查重点:饱和处理与背压行为。Abort抛异常、CallerRuns调用方执行、Discard丢新、DiscardOldest弃队头再试;选法需匹配任务可丢性与背压
本题目录
参考答案
核心回答
以 Java ThreadPoolExecutor 为例,线程池关闭,或者运行时队列无法接收新任务、又无法继续增加工作线程时,会调用 RejectedExecutionHandler。JDK 提供四种内置策略,默认是 AbortPolicy:
AbortPolicy: 向提交任务的线程抛出RejectedExecutionException,让调用方知道这次提交失败,适合需要明确失败反馈的场景。CallerRunsPolicy: 在线程池尚未关闭时,由提交任务的线程直接执行;提交者被占住,有机会减慢后续提交速度。线程池已经关闭时会丢弃任务,并非任何情况下都保证执行。DiscardPolicy: 静默丢弃当前被拒绝的任务。只适合确实允许丢弃、且不依赖该任务完成结果的工作。DiscardOldestPolicy: 线程池未关闭时,移除工作队列头部任务,再尝试提交当前任务;关闭时丢弃当前任务。它丢的是排队任务,不是已经执行最久的任务,重试也不保证成功。
还可以自定义 RejectedExecutionHandler,按业务返回失败、登记可靠待处理记录或做受控降级。策略的选择要回答两个问题:任务能否丢,提交者能否承受同步执行的延迟。
CallerRuns 什么情况下能形成背压
例如批处理线程不断提交任务,当池饱和后,它被迫自己执行一项任务,下一次提交就推迟了,可能自然降低上游速度。
但如果提交者是 Web 请求线程或事件循环,长任务会占住入口执行资源,扩大接口延迟;若提交者已经持有锁或数据库事务,还会延长这些资源的占用。上游如果由大量独立提交者持续灌入,单个提交者变慢也未必能控制总体流量。
因此,CallerRuns 是一种有条件的反馈机制,不等于可靠队列、自动限流或“不丢任务”的承诺。执行线程改变后,线程上下文和调用耗时也可能与原来的异步路径不同。
为什么静默丢弃可能导致 Future 一直等
submit 通常先把任务包装成 FutureTask,再交给 execute。如果拒绝处理只是丢弃这个包装任务,既没有执行,也没有把它标记为完成或取消,返回的 Future 就可能一直未完成,无限等待的 get 也可能一直不返回。
DiscardOldest 丢掉的排队任务也可能存在同样问题。依赖结果的业务应获得明确的拒绝反馈;自定义处理若选择丢弃,需要按约定取消相应 Future、记录结果并通知等待方。调用 get 的等待超时本身不会自动取消任务,仍需根据业务处理后续状态。
DiscardOldest 的“最老”如何理解
内置实现调用的是队列的 poll,再执行一次 execute。对于普通 FIFO 队列,队首通常是最早等待的任务;对于优先级队列,队首可能是优先级最高的任务,所以不能不看队列语义就使用。
而且重试期间其他线程仍可能提交任务,重试可以再次触发拒绝,不能理解为腾出一个位置后新任务就一定成功。自定义策略也不宜无限重试,或在同一个已饱和的线程池里再排一个“处理拒绝”的任务;阻塞等待队列空位还可能与池内任务互相等待。
怎样结合业务选择
需要明确结果的在线请求,可以用快速失败配合上游限流、可控重试;不可丢失的后台工作,应先可靠登记任务,再由可恢复流程处理,不能只靠进程内队列和拒绝回调。
过期采样、可覆盖的刷新等允许丢弃的任务,可以设计丢弃或合并,但仍应统计拒绝数、队列等待时间和任务失败。无界队列通常不会因队满触发饱和拒绝,却可能长期堆积到内存不足;改大队列不等于解决容量问题。
四种策略都是 ThreadPoolExecutor 的静态内部类,实现 RejectedExecutionHandler 接口。业务自定义策略实现的是这个接口的 rejectedExecution 方法,不需要修改线程池源码。
参考资料
- 国内:JavaGuide:Java 线程池详解:用于说明拒绝的触发条件、四种策略及默认处理方式,并连接线程数、队列和生命周期。
- 国内:JavaGuide:Java 线程池最佳实践:用于分析 CallerRuns 的提交者差异、下游容量、排队时效和拒绝监控,解释策略选择的实际代价。
- 国内:美团技术团队:Java 线程池实现原理及其在美团业务中的实践:用于说明任务拒绝在调度保护中的位置,以及通过 RejectedExecutionHandler 定制行为的接口。
- 国外:Oracle:ThreadPoolExecutor,Java SE 25:用于核对四种内置策略、队首丢弃后重试,以及自定义策略需与队列语义匹配的条件。
- 国外:Oracle:Future,Java SE 25:用于区分完成、取消与等待超时,解释被静默丢弃的包装任务为何可能让结果等待无法结束。




