第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 配合合理的超时控制。


相关推荐
唐青枫4 分钟前
别只把大括号当作用域:Zig Block、标签块与控制流实战
后端
TunerT_TQ2 小时前
Microsoft |Playwright CLI 源码静态审阅:从 5 个文件看浏览器自动化工具的工程边界
后端·开源·github
JavaGuide2 小时前
84.5K+ Star!这个开源编程 Agent 控制台,能统一管理 Codex、Claude Code,DeepSeek Harness 也能接入
后端·github
swipe3 小时前
RabbitMQ 实战:AI Agent 里异步处理的标配方案(手把手 + 4 种交换机全解析)
后端·面试·langchain
神奇小汤圆3 小时前
30张图,搞懂分布式追踪系统
后端
量化小c3 小时前
一行代码查 BTCUSDT 和 AAPL 最新价?QuantDash 统一多市场实时行情接口实战
后端·算法·github
沙盘客4 小时前
AFSIM 官方案例库全景与解读方法论
c++·经验分享·后端
abcefg_h4 小时前
MCP 实战指南:如何在项目中使用 Model Context Protocol 及其通信原理
开发语言·后端·golang·mcp
AI多Agent协作实战派4 小时前
AI多Agent协作系统实战(四十八):改个名,整个AI团队都不认识人了
后端