你了解 Java 线程池的原理吗?
- 标签:Java并发、Java
🏛 业务架构师
线程池原理围绕一个 execute 决策链:核心线程未满→创建核心线程;满→任务入队;队列满→创建非核心线程到 max;到 max→执行拒绝策略。回收:非核心线程空闲超 keepAliveSeconds 回收(allowCoreThreadTimeOut 可让核心也回收)。
参数背后是业务负载画像,我按业务类型给经验起点:
- CPU 密集(计算、序列化、加解密):N+1(N=核数)。线程多了只是排队切换,不增吞吐;
- IO 密集(调下游、查库):
N × (1 + 等待时间/计算时间),经验起点 2N~4N——瓶颈在等 IO,多线程就是用 CPU 换吞吐; - 混合型:按业务拆成两个池(计算池 + IO 池)分别调,混在一起的池子参数永远拧巴。
但公式只是起点,真正的定价靠压测与线上画像:观察队列深度趋势、任务耗时分布、线程 CPU 利用率,动态调整(配合配置中心在线改参)。
几个我用血泪换来的要点:
- 队列选择是隐性决策:无界队列(默认 LinkedBlockingQueue)会掩盖过载——任务无声堆积直到 OOM。交易类用有界队列 + 快速失败;队列是缓冲不是蓄水池;
- 拒绝策略=业务态度:Abort 告警人工介入(资金类)、CallerRuns 反压生产端(削峰场景)、Discard 保主弃次(日志埋点)——没有普适答案,按业务价值排序;
- 池子按业务隔离(订单池/通知池),防相互拖累;
- 监控三件套:活跃线程、队列深度、拒绝次数。
一句话:线程池参数是业务负载的函数——画像清楚、压测定价、线上动态调,三步闭环。
🏗 系统架构师
线程池我用七个参数 + 执行流程 + 设计权衡三段讲:
ThreadPoolExecutor 七参数:corePoolSize、maximumPoolSize、keepAliveTime + unit、workQueue(必须有界)、threadFactory(命名!)、handler(拒绝策略)。
执行流程(重点,反直觉处):提交任务 → 核心线程未满则建核心线程 → 满则入队 → 队列满才创建非核心线程直到 max → 线程也满则触发拒绝策略。注意:扩容在入队之后,与很多人直觉相反,队列无界(LinkedBlockingQueue 默认 Integer.MAX_VALUE)意味着 maximumPoolSize 永远不会生效——Executors.newFixedThreadPool 的 OOM 事故根源,所以我规约禁止 Executors 快捷方法,手动 new,队列必须有界。
拒绝策略:AbortPolicy(抛异常,默认)、CallerRunsPolicy(提交者自己跑,天然反压降速,但会阻塞提交线程——网关/IO 线程慎用)、DiscardPolicy(静默丢)、DiscardOldestPolicy(丢队头)。
有界队列的背压传导:队列满 → 拒绝 → 上游感知限流,这是线程池作为局部过载保护器的价值。配套工程实践:按业务隔离线程池(不同业务不共用)、监控队列水位与活跃线程、参数动态化(setCorePoolSize + 配置中心热更)。
💻 全栈开发专家
围绕 7 个参数 + 执行流程 + 一条军规讲:
java
new ThreadPoolExecutor(
corePoolSize, // 核心线程:常驻
maximumPoolSize, // 最大线程
keepAliveTime, unit, // 非核心线程空闲存活时间
workQueue, // 任务队列
threadFactory, // 命名/优先级/daemon
rejectedHandler // 拒绝策略
);提交任务的决策顺序(易错点,不是先开满 max):
text
1. 线程数 < core → 直接新建核心线程执行
2. core 满 → 任务入队列
3. 队列也满 → 新建非核心线程,直到 max
4. max 且队列满 → 执行拒绝策略所以 newFixedThreadPool 用无界队列,永远到不了第 3 步,堆积就 OOM;newCachedThreadPool 用 SynchronousQueue + max=21亿,来一个开一个,打爆系统——这就是禁用 Executors 的原因。
配套要点:
- 核心线程默认不回收,
allowCoreThreadTimeOut(true)可回收; prestartAllCoreThreads()预热,避免冷启动毛刺;- 拒绝策略:CallerRuns(反压,调用线程自己跑,我用于可延迟任务)、Abort(默认,抛异常)、Discard 系(静默丢,危险);生产我自定义:落库/发 MQ + 告警;
- 监控三件套:
getActiveCount()/getQueue().size()/getCompletedTaskCount()定期上报,我接 Grafana 看趋势,配合动态调参。