回答
Java 线程池(ThreadPoolExecutor
)在任务提交时,如果线程数达到 maximumPoolSize
且任务队列已满,就无法继续处理新任务。这时,线程池会根据配置的 拒绝策略(RejectedExecutionHandler) 处理这些任务。Java 提供了四种内置的拒绝策略,同时也支持自定义策略。以下是详细说明:
1. AbortPolicy
(中止策略,默认策略)
- 行为:直接抛出
RejectedExecutionException
异常,拒绝新任务。 - 适用场景:需要明确知道任务被拒绝并采取相应措施的场景。
- 示例:
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1), new ThreadPoolExecutor.AbortPolicy()); executor.execute(() -> System.out.println("Task 1")); executor.execute(() -> System.out.println("Task 2")); try { executor.execute(() -> System.out.println("Task 3")); } catch (RejectedExecutionException e) { System.out.println("Task rejected: " + e); } executor.shutdown();
- 输出:
Task 1 Task rejected: java.util.concurrent.RejectedExecutionException
- 特点:简单粗暴,适合对任务失败敏感的应用。
2. DiscardPolicy
(丢弃策略)
- 行为:默默丢弃新任务,不抛异常,也不执行。
- 适用场景:任务丢失无所谓,或者可以通过其他机制重试的场景。
- 示例:
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1), new ThreadPoolExecutor.DiscardPolicy()); executor.execute(() -> System.out.println("Task 1")); executor.execute(() -> System.out.println("Task 2")); executor.execute(() -> System.out.println("Task 3")); // 被丢弃,无输出 executor.shutdown();
- 输出:
Task 1
- 特点:安静地丢弃任务,不影响现有任务执行。
3. DiscardOldestPolicy
(丢弃最旧任务策略)
- 行为:丢弃任务队列中最旧的任务(即队列头部任务),然后尝试将新任务加入队列。
- 适用场景:优先保证最新任务执行,旧任务不重要时。
- 示例:
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1), new ThreadPoolExecutor.DiscardOldestPolicy()); executor.execute(() -> { try { Thread.sleep(1000); System.out.println("Task 1"); } catch (Exception e) {} }); executor.execute(() -> System.out.println("Task 2")); // 加入队列 executor.execute(() -> System.out.println("Task 3")); // 丢弃 Task 2,Task 3 加入 executor.shutdown();
- 输出:
Task 1 Task 3
- 特点:有利于处理最新任务,但可能丢失早期任务。
4. CallerRunsPolicy
(调用者运行策略)
- 行为:由提交任务的线程(调用者线程)直接执行被拒绝的任务,而不是线程池中的线程。
- 适用场景:希望减缓任务提交速度,避免线程池过载,同时不丢弃任务。
- 示例:
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1), new ThreadPoolExecutor.CallerRunsPolicy()); executor.execute(() -> { try { Thread.sleep(1000); System.out.println("Task 1"); } catch (Exception e) {} }); executor.execute(() -> System.out.println("Task 2")); // 加入队列 executor.execute(() -> System.out.println("Task 3")); // 主线程执行 Task 3 executor.shutdown();
- 输出:
Task 3 // 主线程先执行 Task 1 // 线程池线程执行 Task 2 // 队列任务执行
- 特点:回退到调用者执行,具有一定的负载均衡效果。
5. 自定义拒绝策略
- 实现方式:实现
RejectedExecutionHandler
接口,定义自己的逻辑。 - 示例:
import java.util.concurrent.*; public class CustomRejectionHandler implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println("Task rejected, logging: " + r.toString()); // 可以记录日志、重试或放入备用队列 } public static void main(String[] args) { ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1), new CustomRejectionHandler()); executor.execute(() -> System.out.println("Task 1")); executor.execute(() -> System.out.println("Task 2")); executor.execute(() -> System.out.println("Task 3")); // 触发自定义策略 executor.shutdown(); } }
- 输出:
Task 1 Task rejected, logging: java.lang.ThreadPoolExecutor$Worker@...
- 特点:灵活性高,可根据业务需求定制。
注意事项
- 触发条件:拒绝策略只有在
maximumPoolSize
已满且队列无空间时触发。 - 队列选择影响:
- 无界队列(如
LinkedBlockingQueue
默认配置)不会触发拒绝策略,除非内存耗尽。 - 有界队列(如
ArrayBlockingQueue
)更容易触发拒绝。
- 无界队列(如
- 异常处理:使用
submit()
提交任务时,拒绝异常会被封装在Future.get()
中。
问题分析与知识点联系
“线程池的拒绝策略”是线程池运行机制的重要部分,与问题列表中的多个知识点相关:
-
Java 线程池的原理
拒绝策略是线程池任务处理流程的最后环节,与线程数、队列容量共同决定任务的命运。 -
如何合理设置 Java 线程池的线程数
合理的corePoolSize
和maximumPoolSize
设置可以减少拒绝策略的触发频率,反之则需依赖拒绝策略处理溢出任务。 -
Java 中的阻塞队列
队列类型和容量(如ArrayBlockingQueue
vsSynchronousQueue
)直接影响拒绝策略的触发时机。 -
Java 线程池中 shutdown 与 shutdownNow 的区别
shutdown()
:拒绝新任务但执行队列中任务。shutdownNow()
:立即中断并返回未执行任务,与拒绝策略的处理方式形成对比。
-
Java 创建线程池有哪些方式
Executors
提供的预定义线程池(如newFixedThreadPool
)默认使用AbortPolicy
,了解拒绝策略有助于自定义线程池。
总结来说,拒绝策略是线程池应对过载的保护机制,四种内置策略和自定义方式适用于不同场景。理解其原理和应用需要结合线程池配置、任务负载和队列管理,是优化并发系统的重要知识点。