线程池隔离策略 / 拒绝策略怎么选
一句话速记
线程池隔离 = 不同业务用不同线程池,防止慢接口耗尽线程拖垮快接口(Bulkhead 舱壁模式)。拒绝策略 4 种:AbortPolicy(抛异常,默认)/ CallerRunsPolicy(调用者线程执行,有反压效果)/ DiscardPolicy(静默丢弃)/ DiscardOldestPolicy(丢最旧任务)。实际选型:重要任务用 CallerRunsPolicy + 告警;非重要任务(如日志)用 Discard;任何方案都要加监控。
通俗解释(5 分钟版)
为什么要隔离:
❌ 共用一个线程池(风险):
业务 A(快接口 10ms)
业务 B(慢接口 5s,调 DB)
B 突然 DB 慢,B 的任务积压 → 占满线程池 →
A 的任务也排不进去 → A 也超时 →
原本健康的接口被拖垮
✅ 隔离线程池(舱壁模式):
pool-A(快接口)= 20 线程
pool-B(慢接口)= 10 线程
B 的 pool 满了 → 只影响 B,A 正常服务
线程池参数回顾:
new ThreadPoolExecutor(
corePoolSize, // 核心线程数(常驻)
maximumPoolSize, // 最大线程数
keepAliveTime, // 非核心线程空闲多久回收
unit,
workQueue, // 任务队列(SynchronousQueue / LinkedBlockingQueue 等)
threadFactory, // 线程命名
rejectedHandler // 拒绝策略
)关键细节
1)线程池拒绝时机
任务来了 → 核心线程数够 → 直接用核心线程
核心线程满了 → 加入队列
队列满了 → 创建非核心线程(直到 maxPoolSize)
最大线程数满且队列也满 → 触发拒绝策略
注意顺序:先填满核心线程 → 再填满队列 → 再扩到 max 线程 → 再拒绝。这意味着:
LinkedBlockingQueue(无界)→ 永远不触发拒绝策略(但内存溢出风险)SynchronousQueue→ 没有缓冲,直接扩线程,适合立即执行场景LinkedBlockingQueue(有界)→ 推荐:有缓冲 + 有上限
2)四种拒绝策略
// 1. AbortPolicy(默认):抛 RejectedExecutionException
// 调用方必须捕获并处理
// 适用:明确需要感知失败的场景(接口层面返回错误)
new ThreadPoolExecutor.AbortPolicy()
// 2. CallerRunsPolicy:由提交任务的线程(通常是主线程/web线程)执行
// 效果:天然反压——提交线程被占用,无法继续提交,减慢速度
// 适用:吞吐优先、能接受延迟、不能丢弃的任务
new ThreadPoolExecutor.CallerRunsPolicy()
// 3. DiscardPolicy:静默丢弃新任务
// 无任何通知!适用:日志、埋点等非关键任务
new ThreadPoolExecutor.DiscardPolicy()
// 4. DiscardOldestPolicy:丢弃队列中最旧的任务,然后重试提交
// 适用:需要保留"最新"数据的场景(如实时监控刷新)
// 风险:可能丢弃已经等了很久的任务
new ThreadPoolExecutor.DiscardOldestPolicy()3)自定义拒绝策略(生产推荐)
// 实战最推荐:拒绝时告警 + 降级处理
public class AlertingAbortPolicy implements RejectedExecutionHandler {
private final String poolName;
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 1. 告警(必须!)
log.error("[ThreadPool] {} rejected! queue={}, active={}, max={}",
poolName,
executor.getQueue().size(),
executor.getActiveCount(),
executor.getMaximumPoolSize()
);
Metrics.counter("thread_pool_rejected", "pool", poolName).increment();
// 2. 降级处理(根据业务选择)
throw new RejectedExecutionException("ThreadPool " + poolName + " overloaded");
}
}4)线程池隔离的粒度
按业务类型隔离:
┌────────────────────────────────────────────────────────┐
│ 核心业务(订单/支付) pool-critical 20核+200队列 │
│ 异步通知(短信/邮件) pool-notify 10核+500队列 │
│ 报表/数据导出 pool-report 5核+50队列 │
│ 外部 HTTP 调用 pool-http 50核+100队列 │
│ 内部 DB 查询 pool-db 30核+200队列 │
└────────────────────────────────────────────────────────┘
Hystrix/Resilience4j 的线程池隔离(Circuit Breaker):
// Resilience4j 线程池隔离示例
ThreadPoolBulkhead bulkhead = ThreadPoolBulkhead.of("db-pool",
ThreadPoolBulkheadConfig.custom()
.maxThreadPoolSize(10)
.coreThreadPoolSize(5)
.queueCapacity(100)
.build()
);
// 每个下游服务单独一个 bulkhead,互不影响5)线程池参数调优经验
CPU 密集型:
corePoolSize = CPU核数 (或 CPU核数+1)
maxPoolSize = CPU核数 * 2
queue = 小(SynchronousQueue 或小有界队列)
I/O 密集型:
corePoolSize = CPU核数 * 2~4(甚至更多,取决于 I/O 比例)
maxPoolSize = corePoolSize * 2
queue = 有界(防止无限积压)
经验公式:
线程数 = CPU核数 × (1 + I/O等待时间 / CPU计算时间)
如果 I/O 占 90%,CPU 占 10%,8核机器 → 8 * (1 + 9) = 80 线程
监控指标:
# 线程池健康监控(Spring Actuator + Micrometer)
executor.active # 当前活跃线程数
executor.queued # 队列中等待任务数
executor.completed # 已完成任务数
executor.pool.size # 当前池大小
thread_pool_rejected # 拒绝次数(告警关键指标)
# 告警阈值参考:
# queued > maxPoolSize * 2 → 预警
# rejected > 0 → 立即告警6)动态线程池(生产最佳实践)
// 固定线程数不好调,推荐动态配置
// 美团 DynamicTp、自研 + Apollo/Nacos 配置中心
ThreadPoolExecutor pool = new ThreadPoolExecutor(...);
// 运行时调整,不重启
pool.setCorePoolSize(newCore);
pool.setMaximumPoolSize(newMax);
// 注意:setMaximumPoolSize 必须 >= setCorePoolSize
// 配合告警:rejected > 0 → 人工介入调参或扩容延伸追问
- Q:CallerRunsPolicy 有什么风险? → 主线程(如 Tomcat 请求线程)被占用来执行业务任务,导致 HTTP 请求无法接收——相当于整个服务器被”降速”。适合吞吐敏感、能接受响应变慢的批量处理场景;不适合对延迟有 SLA 要求的接口。
- Q:为什么线程池要命名线程(ThreadFactory)?
→ 排查时
jstack看到pool-1-thread-1完全不知道是哪个业务,order-db-pool-1一目了然。必须命名。 - Q:
Executors.newFixedThreadPool(n)有什么坑? → 内部用的是无界LinkedBlockingQueue,队列永不满 → 拒绝策略永不触发 → 任务无限积压 → 内存 OOM。生产不推荐 Executors 工厂方法,用 ThreadPoolExecutor 直接构造并指定有界队列。
我的记法
- 隔离 = 舱壁模式:慢接口不拖垮快接口
- 拒绝策略选型:重要任务
CallerRunsPolicy(反压)/ 非关键任务Discard/ 默认用自定义告警策略 - 拒绝触发条件:核心线程满 → 队列满 → max线程满 → 拒绝
Executors.newFixedThreadPool无界队列 = 生产禁忌- 线程一定要命名(ThreadFactory),监控 rejected 指标
- 一句话:「线程池隔离防雪崩,拒绝策略看业务——重要任务反压,非关键任务丢弃,所有策略必须告警」
状态
- 已背速记
- 能讲通俗版
- 能答线程池参数调优追问