第14章 线程池 RejectedExecutionException 与拒绝策略源码

第14章 线程池 RejectedExecutionException 与拒绝策略源码

14.1 触发条件的精确边界:实测验证

很多人对"线程池什么时候会拒绝任务"只有模糊印象,用最小化参数实测精确边界:

java 复制代码
ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
        new ArrayBlockingQueue<>(1)); // 核心线程1,最大线程1,队列容量1
for (int i = 0; i < 5; i++) {
    pool.execute(task_i);
}

实测结果(JDK 21):

ini 复制代码
submitted 0    ← 核心线程立即执行(active threads = 1)
submitted 1    ← 进入队列(queued tasks = 1)
task 2 rejected: ... pool size = 1, active threads = 1, queued tasks = 1 ...
task 3 rejected: ...
task 4 rejected: ...
task 0 done
task 1 done

精确验证了 ThreadPoolExecutor 的任务分发逻辑(源码 execute() 方法的三段式判断):

  1. 当前运行线程数 < corePoolSize → 创建新线程直接执行。
  2. 否则尝试放入队列 → 队列未满则入队等待。
  3. 队列也满了 → 尝试创建新线程(前提是当前线程数 < maximumPoolSize)。
  4. 以上都不满足(本例中 maximumPoolSize 也等于 1,无法再创建新线程)→ 触发拒绝策略。

本例中 core=max=1,第 3 步永远不可能成立,所以核心线程执行 1 个 + 队列容纳 1 个之后,第 3、4、5 个任务必然全部被拒绝,异常信息里明确带出了拒绝时刻线程池的完整状态快照(pool size / active threads / queued tasks / completed tasks),这是排查线程池打满问题时最直接的诊断依据。

14.2 四种内置拒绝策略源码级对比

java 复制代码
// 1. AbortPolicy(默认):直接抛异常,最"暴力"但也最能第一时间暴露问题
public static class AbortPolicy implements RejectedExecutionHandler {
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString());
    }
}

// 2. CallerRunsPolicy:把任务丢回给"提交任务的线程"自己同步执行
public static class CallerRunsPolicy implements RejectedExecutionHandler {
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            r.run(); // 注意:是直接调用 run(),不是提交到线程池,因此这里会阻塞调用线程
        }
    }
}

// 3. DiscardPolicy:静默丢弃,不抛异常也不记录任何日志------生产环境慎用,容易丢任务却毫无感知
public static class DiscardPolicy implements RejectedExecutionHandler {
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { }
}

// 4. DiscardOldestPolicy:丢弃队列中最老(排队最久)的任务,把位置让给新任务
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll(); // 从队列头部丢弃一个
            e.execute(r);         // 重新提交当前任务
        }
    }
}

CallerRunsPolicy 的"降速"效果原理 :当线程池打满,提交任务的线程(通常是接收请求的主线程,比如 Web 容器的请求处理线程)会被迫自己同步执行这个任务,这段时间内该线程无法再去接收/提交新的请求,客观上起到了给上游限速的作用------这是一种朴素但有效的背压(backpressure)机制,代价是可能拖慢请求处理线程的响应速度。

生产环境选型建议 :核心业务链路通常不建议用 DiscardPolicy (静默丢任务,出问题很难被发现,往往是通过"业务数据对不上"这种滞后信号才暴露),更推荐 AbortPolicy 配合主动 catch 后走降级逻辑(比如写入延迟队列/落库后续补偿),或者 CallerRunsPolicy 配合合理的超时控制。


相关推荐
陈随易3 小时前
在Finch用了62亿词元,我认为这是新一代Agent工具之神
前端·人工智能·后端
wing984 小时前
从codex转战workbuddy使用一周的感受
前端·人工智能·后端
EatFan4 小时前
Java接入支付宝 JSAPI 支付保姆教程(二):流程讲解与前后端代码讲解
前端·spring boot·后端·微信小程序·小程序·uni-app
步行cgn5 小时前
Spring 报错:No bean class specified on bean definition 的原因与解决
java·后端·spring
凤山老林5 小时前
Spring Boot 整合 Flowable 的企业级落地指南
数据库·spring boot·后端·flowable·工作流
苏三说技术5 小时前
Kafka已正式接入AI
后端
企业数字化笔记5 小时前
AI写的系统出现504怎么办?接口超时和数据库慢查询排查
数据库·后端
IT_陈寒5 小时前
Redis的Set操作居然能把我的服务整挂了?
前端·人工智能·后端
momo061175 小时前
Redis新手入门 -- 学习笔记
redis·后端
井云AI5 小时前
vendor 资源包是什么:井云 DSH 客户端打包为什么离不开它
后端·智能体小程序