Skip to content

你了解 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 看趋势,配合动态调参。