线程池四种拒绝策略(ThreadPoolExecutor 内置)

📅 2026/7/5 6:42:48 👁️ 阅读次数 📝 编程学习
线程池四种拒绝策略(ThreadPoolExecutor 内置)

线程池四种拒绝策略(ThreadPoolExecutor 内置)

当满足线程池饱和条件时触发拒绝:

  1. 核心线程全部繁忙
  2. 阻塞队列已满
  3. 最大线程数已达到上限
    此时新提交任务会走拒绝策略,JDK 内置 4 种实现。

前置基础构造示意

// 核心线程2,最大5,队列容量3,空闲线程存活0sThreadPoolExecutorexecutor=newThreadPoolExecutor(2,5,0L,TimeUnit.MILLISECONDS,newArrayBlockingQueue<>(3));// 设置拒绝策略executor.setRejectedExecutionHandler(newThreadPoolExecutor.CallerRunsPolicy());

饱和临界点:同时提交超过maxPoolSize + queueSize = 5+3=8个任务,第9个开始触发拒绝策略。


1. AbortPolicy(默认策略:直接抛出异常)

代码

executor.setRejectedExecutionHandler(newThreadPoolExecutor.AbortPolicy());

行为

直接抛出RejectedExecutionException,中断当前任务提交,不执行任务。

示例

for(inti=0;i<10;i++){executor.submit(()->{try{Thread.sleep(10000);}catch(InterruptedExceptione){e.printStackTrace();}});}

提交第9个任务直接抛异常,程序不捕获会中断业务流程。

使用场景

  • 任务不允许丢失,且调用方必须感知失败、主动降级重试;
  • 同步阻塞业务、支付、核心数据处理,不允许静默丢失任务;
  • 小型同步接口,任务量可控,异常统一捕获做告警/重试。

2. DiscardPolicy(丢弃当前新任务,无任何提示)

代码

executor.setRejectedExecutionHandler(newThreadPoolExecutor.DiscardPolicy());

行为

默默丢掉新来的任务,不抛异常、无日志、无告警,调用方无感知。

风险

任务永久丢失,业务数据缺失很难排查。

使用场景

  • 非核心、可丢弃的统计上报、埋点日志、实时监控采集;
  • 允许数据丢失、对一致性无要求的边缘辅助任务。

3. DiscardOldestPolicy(丢弃队列最老未执行任务,执行新任务)

代码

executor.setRejectedExecutionHandler(newThreadPoolExecutor.DiscardOldestPolicy());

行为

取出阻塞队列头部排队最久的任务丢弃,再尝试提交当前新任务。

使用场景

  • 实时数据、最新状态覆盖旧数据:如设备实时状态上报、行情推送;
  • 旧任务无保留价值,最新数据才有效,不需要历史排队任务。

不适用场景

订单、入库、消息消费等顺序/历史任务不能丢的业务。


4. CallerRunsPolicy(调用者线程执行,你提问中的策略)

代码(你给出的)

executor.setRejectedExecutionHandler(newThreadPoolExecutor.CallerRunsPolicy());

行为

不丢任务、不抛异常,把当前任务交给提交任务的主线程同步执行
举例子:主线程调用executor.submit(),池子满了,任务直接在主线程跑,阻塞主线程直到任务完成。

效果

  1. 不会丢失任何任务;
  2. 提交线程被阻塞,天然限流;
  3. 减缓任务提交速度,给线程池留出时间处理堆积。

示例演示

ThreadPoolExecutorexecutor=newThreadPoolExecutor(2,5,0L,TimeUnit.MILLISECONDS,newArrayBlockingQueue<>(3),newThreadPoolExecutor.CallerRunsPolicy());// 模拟10个长耗时任务for(inti=0;i<10;i++){inttaskId=i;System.out.println("提交任务:"+taskId+" 提交线程:"+Thread.currentThread().getName());executor.submit(()->{System.out.println("执行任务:"+taskId+" 执行线程:"+Thread.currentThread().getName());try{Thread.sleep(3000);}catch(InterruptedExceptione){}});}

输出现象:前8个由池内线程执行,第9、10个任务打印执行线程为main,主线程同步执行。

使用场景

  1. 流量削峰、不允许丢任务:文件同步、数据库批量入库、定时采集任务(你项目采集接口非常适配);
  2. 无独立重试机制、任务丢失会造成业务数据缺失;
  3. 接口流量突增时,自动降级为调用线程执行,起到限流保护线程池;
  4. Spring 内置定时池、普通业务异步线程池常用该策略。

缺点

高并发下主线程阻塞,接口响应时间变长,吞吐量下降。


四种策略对比表

策略处理逻辑是否丢任务是否抛异常典型适用场景
AbortPolicy抛异常拒绝丢当前任务核心交易、需感知失败的同步业务
DiscardPolicy静默丢弃新任务丢当前任务非核心埋点、监控日志
DiscardOldestPolicy丢弃队列最老任务丢旧任务实时状态、行情推送
CallerRunsPolicy调用线程同步执行不丢失数据采集、入库、定时任务、流量削峰

生产使用建议(结合你项目采集定时任务)

你的业务是http采集定时任务,推荐CallerRunsPolicy

  1. 采集数据不能丢,丢弃会造成数据断层;
  2. 池子打满时主线程同步执行,自动限流,避免无限创建任务OOM;
  3. 无需额外编写重试、丢失补偿逻辑。

拓展:自定义拒绝策略(生产常用兜底)

内置策略无法满足告警需求时自定义,打印日志+推送告警:

publicclassCustomRejectHandlerimplementsRejectedExecutionHandler{@OverridepublicvoidrejectedExecution(Runnabler,ThreadPoolExecutorexecutor){log.error("线程池饱和,任务被拒绝,核心:{},最大:{},队列容量:{}",executor.getCorePoolSize(),executor.getMaximumPoolSize(),executor.getQueue().size());// 推送钉钉/短信告警// 可选:本地持久化任务,后续补偿thrownewRuntimeException("任务提交失败,线程池已满");}}