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


相关推荐
Leo2821 小时前
LangGraph:把 Agent 从“会回答”推进到“可控执行”的工程架构
后端
铁皮饭盒2 小时前
网页端OCR, 加载6mb大小模型, 又快又准, 百度这次真香
前端·后端
程序员-Benothing2 小时前
Java集合:List实现类深度对比
java·开发语言·后端·面试·list
云浪2 小时前
给 AI Agent 加上语音交互:ASR + 流式 TTS 实战
前端·人工智能·后端
卷无止境2 小时前
当代码要上线前,谁在替你把关?——Python安全审查的方法论与实践
后端·python
卷无止境2 小时前
Python Dataclasses:让类定义回归简洁的艺术
后端·python
前端工作日常12 小时前
我学习到的Java 的 Service 分层:itf 和 impl 到底是什么?
java·后端
前端工作日常13 小时前
我学习到的JIT即时编译与机器码缓存失效
java·后端