线程池中的拒绝策略有哪些?请列举并说明其适用场景。
参考回答
线程池中的拒绝策略(RejectedExecutionHandler
)用于处理当任务无法被线程池执行时的情况,例如任务队列已满且线程数已达到最大线程数。ThreadPoolExecutor
提供了以下几种内置的拒绝策略,每种策略适用于不同的场景:
- AbortPolicy(默认策略)
- 行为:抛出
RejectedExecutionException
异常,通知任务提交方任务无法被执行。 - 适用场景:适用于任务必须被执行、不能丢失的场景,例如关键性任务。如果任务被拒绝,需要开发者捕获异常并进行处理。
- 行为:抛出
- CallerRunsPolicy
- 行为:由提交任务的线程(调用者线程)执行任务,而不是线程池中的线程。
- 适用场景:适用于可以将任务回退给调用方执行的场景,通常用来降低线程池的负载,但可能会导致调用线程的性能下降。
- DiscardPolicy
- 行为:直接丢弃任务,不抛出异常,也不通知任务提交方。
- 适用场景:适用于可以容忍部分任务丢失的场景,例如日志记录或监控统计,任务丢失不会影响系统的正常运行。
- DiscardOldestPolicy
- 行为:丢弃任务队列中等待最久的任务,然后尝试重新提交当前任务。
- 适用场景:适用于需要保证新任务优先被执行的场景,例如实时数据处理的任务。
详细讲解与拓展
1. 内置拒绝策略源码解析
1.1 AbortPolicy
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
- 抛出
RejectedExecutionException
,终止任务提交流程。 - 优点:明确通知调用方任务被拒绝。
- 缺点:需要调用方显式捕获异常,否则可能导致程序崩溃。
1.2 CallerRunsPolicy
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
- 调用线程执行任务,如果线程池已经关闭,则任务不会被执行。
- 优点:避免任务丢失,并减少线程池的负载。
- 缺点:可能拖慢调用线程的执行速度,影响其他任务。
1.3 DiscardPolicy
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
// 直接丢弃任务
}
}
- 任务直接被丢弃,没有任何通知。
- 优点:简单高效,不影响线程池运行。
- 缺点:任务丢失无法感知,不适合关键性任务。
1.4 DiscardOldestPolicy
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll(); // 移除最早的任务
e.execute(r); // 尝试执行新任务
}
}
}
- 移除队列中最早的任务,然后重新尝试提交新任务。
- 优点:优先处理最新任务,适合实时性要求高的场景。
- 缺点:可能丢弃尚未完成的重要任务。
2. 自定义拒绝策略
如果内置策略不能满足需求,可以通过实现 RejectedExecutionHandler
接口来定义自己的拒绝策略。
示例:记录被拒绝任务的日志
public class LoggingRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.err.println("Task " + r.toString() + " was rejected.");
}
}
将自定义拒绝策略应用于线程池:
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
Executors.defaultThreadFactory(),
new LoggingRejectedExecutionHandler()
);
3. 应用场景分析与选择
拒绝策略 | 适用场景 |
---|---|
AbortPolicy | 任务必须被执行,不能丢失的场景。例如支付任务、订单处理等。 |
CallerRunsPolicy | 可以由调用方执行任务,适合需要平衡线程池负载的场景。例如批量处理任务的降级场景。 |
DiscardPolicy | 非关键任务,可以容忍任务丢失的场景。例如日志记录、监控统计等。 |
DiscardOldestPolicy | 实时性要求高,需要优先执行最新任务的场景。例如实时数据流处理、交易系统中的实时行情更新。 |
4. 示例代码
以下代码演示不同拒绝策略的效果:
import java.util.concurrent.*;
public class RejectedExecutionDemo {
public static void main(String[] args) {
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy(); // 可更换策略
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2), // 有界队列
Executors.defaultThreadFactory(),
handler
);
for (int i = 1; i <= 10; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("Task " + taskId + " is running by " + Thread.currentThread().getName());
try {
Thread.sleep(2000); // 模拟任务执行
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executor.shutdown();
}
}
更换不同拒绝策略,观察以下行为:
AbortPolicy
:抛出异常,终止程序运行。CallerRunsPolicy
:多余任务由调用线程执行,程序可能变慢。DiscardPolicy
:多余任务直接丢弃。DiscardOldestPolicy
:最早的任务被丢弃。
总结
- 拒绝策略的核心作用:在线程池资源耗尽时,防止任务无限积压,合理处理超出负载的任务。
- 内置策略:
AbortPolicy
用于关键任务,CallerRunsPolicy
平衡负载,DiscardPolicy
和DiscardOldestPolicy
适合非关键任务。 - 选择建议:根据业务需求选择合适的拒绝策略,关键任务需优先保证执行,非关键任务可以选择丢弃策略。
- 自定义策略:在特殊场景下,可以实现自定义策略满足业务需求。