Java 线程池拒绝策略
在 Java 线程池中,拒绝策略(RejectedExecutionHandler)用于处理当线程池无法接收新任务时的情况(通常是因为线程池已达最大线程数且任务队列已满)。Java 内置了 4 种拒绝策略,同时也支持自定义策略,选择合适的策略需结合业务场景。
一、内置拒绝策略及适用场景
Java 线程池的拒绝策略都实现了 RejectedExecutionHandler
接口,以下是 4 种内置策略的特点和适用场景:
1. AbortPolicy
(默认策略)
- 行为:直接抛出
RejectedExecutionException
异常,阻止系统正常运行。 - 适用场景:需要明确知道任务被拒绝的场景,且不允许任务丢失(例如核心业务流程,必须处理所有任务,失败时需告警或重试)。
- 示例:
java
运行
new ThreadPoolExecutor.AbortPolicy()
2. CallerRunsPolicy
- 行为:让提交任务的线程(调用者线程)亲自执行被拒绝的任务。
- 适用场景:任务量不大、并发度不高的场景,或希望避免任务丢失且能容忍提交线程被阻塞的情况(例如非核心任务,允许通过阻塞调用者来 “节流”,避免系统过载)。
- 优点:通过阻塞提交者,间接降低任务提交速度,给线程池缓冲时间。
- 示例:
java
运行
new ThreadPoolExecutor.CallerRunsPolicy()
3. DiscardPolicy
- 行为:直接丢弃被拒绝的任务,不抛出异常,也不做任何处理。
- 适用场景:任务无关紧要、允许丢失的场景(例如日志收集、统计上报等非核心任务,丢失部分数据不影响主流程)。
- 注意:任务丢失后无任何提示,需谨慎使用。
- 示例:
java
运行
new ThreadPoolExecutor.DiscardPolicy()
4. DiscardOldestPolicy
- 行为:丢弃任务队列中最旧的任务(即将被执行的任务),然后尝试提交新任务。
- 适用场景:任务队列是优先级队列或需要处理最新任务的场景(例如实时数据处理,旧数据的价值低于新数据)。
- 注意:可能会丢弃重要的旧任务,需确保业务允许这种取舍。
- 示例:
java
运行
new ThreadPoolExecutor.DiscardOldestPolicy()
二、自定义拒绝策略
如果内置策略无法满足需求,可以通过实现 RejectedExecutionHandler
接口自定义拒绝策略,例如:
- 记录被拒绝的任务到日志,后续人工处理
- 将任务保存到持久化存储(如数据库、消息队列),待线程池空闲后重试
- 抛出自定义异常,携带更多业务信息
示例:自定义拒绝策略(记录日志并保存任务)
java
运行
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;public class CustomRejectPolicy implements RejectedExecutionHandler {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {// 1. 记录日志System.err.println("任务 " + r + " 被拒绝,线程池状态:" + "核心线程数=" + executor.getCorePoolSize() + ", 活跃线程数=" + executor.getActiveCount() + ", 队列大小=" + executor.getQueue().size());// 2. 可选:将任务保存到数据库或消息队列,后续重试// saveTaskToDB(r);}
}// 使用自定义策略
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS,new ArrayBlockingQueue<>(2),new CustomRejectPolicy() // 应用自定义策略
);
三、拒绝策略选择原则
核心任务 vs 非核心任务:
- 核心任务(如支付、订单处理):优先选择
AbortPolicy
(明确失败)或自定义策略(确保任务不丢失)。 - 非核心任务(如日志、通知):可选择
DiscardPolicy
或DiscardOldestPolicy
。
- 核心任务(如支付、订单处理):优先选择
系统负载敏感度:
- 对系统负载敏感(不希望提交线程被阻塞):避免
CallerRunsPolicy
。 - 允许通过阻塞限流:
CallerRunsPolicy
是不错的选择。
- 对系统负载敏感(不希望提交线程被阻塞):避免
任务时效性:
- 新任务比旧任务重要(如实时监控):
DiscardOldestPolicy
更合适。
- 新任务比旧任务重要(如实时监控):
可观测性:
- 任何拒绝策略都应配合监控或日志,确保能感知任务被拒绝的情况(内置的
DiscardPolicy
和DiscardOldestPolicy
无提示,需额外处理)。
- 任何拒绝策略都应配合监控或日志,确保能感知任务被拒绝的情况(内置的
四、总结
线程池拒绝策略的选择本质是在任务丢失、系统稳定性、业务连续性之间做权衡。实际开发中,建议:
- 核心业务优先使用
AbortPolicy
或自定义策略(确保任务可追踪)。 - 非核心业务根据时效性选择
DiscardPolicy
或DiscardOldestPolicy
。 - 低并发场景或需要 “自我保护” 时,考虑
CallerRunsPolicy
。 - 始终通过监控(如线程池活跃线程数、队列长度)提前规避任务被拒绝的情况,拒绝策略应作为 “最后一道防线”。in