线程池中的拒绝策略有哪些?请列举并说明其适用场景。

参考回答

线程池中的拒绝策略(RejectedExecutionHandler)用于处理当任务无法被线程池执行时的情况,例如任务队列已满且线程数已达到最大线程数。ThreadPoolExecutor 提供了以下几种内置的拒绝策略,每种策略适用于不同的场景:

  1. AbortPolicy(默认策略)
    • 行为:抛出 RejectedExecutionException 异常,通知任务提交方任务无法被执行。
    • 适用场景:适用于任务必须被执行、不能丢失的场景,例如关键性任务。如果任务被拒绝,需要开发者捕获异常并进行处理。
  2. CallerRunsPolicy
    • 行为:由提交任务的线程(调用者线程)执行任务,而不是线程池中的线程。
    • 适用场景:适用于可以将任务回退给调用方执行的场景,通常用来降低线程池的负载,但可能会导致调用线程的性能下降。
  3. DiscardPolicy
    • 行为:直接丢弃任务,不抛出异常,也不通知任务提交方。
    • 适用场景:适用于可以容忍部分任务丢失的场景,例如日志记录或监控统计,任务丢失不会影响系统的正常运行。
  4. 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 平衡负载,DiscardPolicyDiscardOldestPolicy 适合非关键任务。
  • 选择建议:根据业务需求选择合适的拒绝策略,关键任务需优先保证执行,非关键任务可以选择丢弃策略。
  • 自定义策略:在特殊场景下,可以实现自定义策略满足业务需求。

发表评论

后才能评论