java线程池拒绝策略在核心线程满、有界队列满且活跃线程达maximumpoolsize时触发;四种内置策略:abortpolicy抛异常(适合关键路径)、callerrunspolicy由提交线程执行(反压限流)、discardpolicy静默丢弃(适合可丢任务)、discardoldestpolicy丢队首再提交(适合时效敏感任务)。

Java 线程池的拒绝策略,本质是当任务无法被接纳时的兜底响应机制——不是“配了就行”的开关,而是系统水位超限时的关键决策点。它只在三个条件同时满足时触发:核心线程已满、工作队列已满(且为有界队列)、当前线程数已达 maximumPoolSize。理解这点,才能避免策略形同虚设。
四种内置拒绝策略各有什么行为和适用场景
每种策略对应一种明确的失败应对逻辑,选错等于埋雷:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
AbortPolicy(默认)
直接抛出RejectedExecutionException。
适合关键路径任务,比如支付扣款、库存预占——调用方必须感知失败并做重试或降级。
注意:若上层没捕获该异常,任务就真丢了,还可能引发链路中断。CallerRunsPolicy
由提交任务的线程(如 Web 容器线程、调度线程)同步执行该任务。
本质是反压机制:让上游变慢,自然降低提交速率。
适合离线任务、批量补单等非实时路径;但严禁用于 HTTP 请求线程或 Netty EventLoop,否则直接拖垮响应。DiscardPolicy
静默丢弃新任务,不抛异常、不记录、不补偿。
适合日志上报、监控埋点、缓存预热等允许丢失的辅助任务。
风险在于“丢得无声无息”,必须搭配队列长度、活跃线程数等指标监控,否则故障难定位。DiscardOldestPolicy
丢弃队列中最老的一个任务(queue.poll()),再尝试重新提交当前任务。
适合时效敏感型任务,如行情推送、状态刷新——旧数据过期无价值,新数据优先保障。
注意:对SynchronousQueue无效(它没容量,poll()总返回null);也无日志,丢弃行为不可追溯。
自定义拒绝策略要解决三个实际问题
写一个 implements RejectedExecutionHandler 的类只是第一步。生产可用的自定义策略,必须覆盖可观测性、可恢复性、线程安全性:
-
记录完整上下文,不止是“任务被拒”
至少包含:任务类型(如instanceof PayCallbackTask)、业务 ID(订单号/用户 ID)、提交时间戳、executor.getQueue().size()、executor.getActiveCount()、是否已 shutdown。
示例片段:log.warn("REJECT: task={}, orderId={}, queueSize={}, active={}", r.getClass().getSimpleName(), extractOrderId(r), executor.getQueue().size(), executor.getActiveCount()); -
补偿动作必须带超时与降级,不能阻塞提交线程
比如发 MQ、写 DB、落盘等操作,必须:- 使用异步非阻塞方式(如
CompletableFuture.runAsync(..., diskExecutor)) - 设置超时(如
rocketMQTemplate.asyncSend(..., timeout = 300)) - 降级路径明确(MQ 失败 → 写本地 JSON 文件;文件写满 → 丢弃并告警)
- 绝对禁止在
rejectedExecution中调用executor.execute(r),否则极易递归触发拒绝甚至死锁。
- 使用异步非阻塞方式(如
-
区分任务语义,按需分流处理
不是所有任务都该一视同仁地丢或重试:- 关键任务(如支付回调)→ 封装成
SerializableTask发 Kafka 延迟重试 Topic - 可丢任务(如埋点)→ 打点监控
metrics.counter("rejected", "type", "metric").increment() - 需调用方响应的任务 → 抛出自定义异常
TaskRejectedException.withTraceId(traceId),由上层统一熔断或返回兜底值
- 关键任务(如支付回调)→ 封装成
自定义策略如何正确接入线程池
- 必须通过
ThreadPoolExecutor构造函数第 7 个参数传入,初始化后不可变更:new ThreadPoolExecutor( core, max, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(100), threadFactory, new CustomRejectedExecutionHandler() // ← 这里 ); -
rejectedExecution方法由提交线程同步调用(比如你主线程调submit()时触发),不是工作线程执行,所以耗时操作会卡住调用方。 - JDK 21 之前没有
setRejectedExecutionHandler()方法,别试图运行时替换。
不复杂但容易忽略。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










