返回
Java ThreadPoolExecutor 的拒绝策略大剖析
后端
2024-02-09 14:57:34
Java 中的 ThreadPoolExecutor 类提供了四种拒绝策略,用于处理无法执行的新任务。本文将详细分析这四种拒绝策略,帮助您在实际应用中选择最合适的策略。
拒绝策略概述
当线程池中的所有线程都处于繁忙状态,并且任务队列已满时,新提交的任务将被拒绝。拒绝策略决定了如何处理这些被拒绝的任务。
四种拒绝策略
ThreadPoolExecutor 类提供了以下四种拒绝策略:
- 饱和策略 (AbortPolicy):此策略会直接抛出 RejectedExecutionException 异常。这是最简单的策略,但也是最鲁莽的策略,可能导致应用程序崩溃。
- 丢弃策略 (DiscardPolicy):此策略会直接丢弃新任务,不抛出任何异常。这是一种非常粗暴的策略,可能会导致任务丢失。
- 调用者运行策略 (CallerRunsPolicy):此策略会让调用者线程自己执行任务。这是一种比较友好的策略,但可能会导致调用者线程阻塞。
- 丢弃最旧策略 (DiscardOldestPolicy):此策略会丢弃线程池中最旧的任务,让新任务得以执行。这是一种比较折中的策略,既不会导致应用程序崩溃,也不会导致任务丢失。
如何选择合适的拒绝策略
在选择拒绝策略时,需要考虑以下几个因素:
- 应用程序的容错性 :应用程序对任务丢失的容忍程度如何?如果应用程序不能容忍任务丢失,那么就应该选择饱和策略或调用者运行策略。
- 任务的优先级 :任务的优先级如何?如果任务的优先级很高,那么就应该选择饱和策略或调用者运行策略。
- 线程池的负载情况 :线程池的负载情况如何?如果线程池经常处于满负荷状态,那么就应该选择丢弃策略或丢弃最旧策略。
拒绝策略的示例
以下示例演示了如何使用不同的拒绝策略来处理被拒绝的任务:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
// 创建一个线程池,使用饱和策略
ExecutorService executorService = Executors.newFixedThreadPool(10, new ThreadPoolExecutor.AbortPolicy());
// 提交 20 个任务
for (int i = 0; i < 20; i++) {
executorService.submit(() -> {
try {
TimeUnit.SECONDS.sleep(1);
System.out.println("任务 " + Thread.currentThread().getName() + " 执行完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executorService.shutdown();
}
}
运行以上代码,会看到以下输出:
任务 pool-1-thread-1 执行完成
任务 pool-1-thread-2 执行完成
任务 pool-1-thread-3 执行完成
任务 pool-1-thread-4 执行完成
任务 pool-1-thread-5 执行完成
任务 pool-1-thread-6 执行完成
任务 pool-1-thread-7 执行完成
任务 pool-1-thread-8 执行完成
任务 pool-1-thread-9 执行完成
任务 pool-1-thread-10 执行完成
java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@6e278309 rejected from java.util.concurrent.ThreadPoolExecutor@3f5c3077[Running, pool size = 10, active threads = 10, queued tasks = 0, completed tasks = 10]
java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@78d4fb99 rejected from java.util.concurrent.ThreadPoolExecutor@3f5c3077[Running, pool size = 10, active threads = 10, queued tasks = 0, completed tasks = 10]
java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@53c0005a rejected from java.util.concurrent.ThreadPoolExecutor@3f5c3077[Running, pool size = 10, active threads = 10, queued tasks = 0, completed tasks = 10]
java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@6b6a7590 rejected from java.util.concurrent.ThreadPoolExecutor@3f5c3077[Running, pool size = 10, active threads = 10, queued tasks = 0, completed tasks = 10]
java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@7b994514 rejected from java.util.concurrent.ThreadPoolExecutor@3f5c3077[Running, pool size = 10, active threads = 10, queued tasks = 0, completed tasks = 10]
可以看到,当线程池满负荷时,新提交的任务都被拒绝了,并且抛出了 RejectedExecutionException 异常。
如果使用丢弃策略,则新提交的任务会被直接丢弃,不会抛出任何异常。
如果使用调用者运行策略,则新提交的任务会被调用者线程自己执行。
如果使用丢弃最旧策略,则线程池中最旧的任务会被丢弃,让新任务得以执行。