Skip to content

Commit cffb9d3

Browse files
committed
docs: 线程池文章新增生命周期状态、Worker机制、拒绝策略应用场景三节
1 parent 744d1dd commit cffb9d3

File tree

1 file changed

+41
-1
lines changed

1 file changed

+41
-1
lines changed

docs/java/concurrent/java-thread-pool-summary.md

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,32 @@ public class ScheduledThreadPoolExecutor
136136

137137
![线程池各个参数的关系](https://oss.javaguide.cn/github/javaguide/java/concurrent/relationship-between-thread-pool-parameters.png)
138138

139+
### 线程池生命周期状态
140+
141+
`ThreadPoolExecutor` 使用 `ctl` 变量(`AtomicInteger` 类型)同时管理线程池的运行状态和工作线程数量。线程池共有 5 种状态:
142+
143+
- **运行中(`RUNNING`**:接受新任务,并处理队列中的任务。线程池创建后的初始状态。
144+
- **关闭(`SHUTDOWN`**:不再接受新任务,但会继续处理队列中已有的任务。调用 `shutdown()` 后进入。
145+
- **停止(`STOP`**:不接受新任务,不处理队列中的任务,并尝试中断正在执行的任务。调用 `shutdownNow()` 后进入。
146+
- **整理中(`TIDYING`**:所有任务已终止,工作线程数为 0,即将执行 `terminated()` 钩子方法。
147+
- **已终止(`TERMINATED`**`terminated()` 方法执行完毕,线程池彻底终结。
148+
149+
状态只能单向流转:运行中(`RUNNING`)→ 关闭(`SHUTDOWN`)→ 整理中(`TIDYING`)→ 已终止(`TERMINATED`),或者运行中(`RUNNING`)→ 停止(`STOP`)→ 整理中(`TIDYING`)→ 已终止(`TERMINATED`)。在关闭(`SHUTDOWN`)状态下再调用 `shutdownNow()` 也会转为停止(`STOP`)。
150+
151+
`shutdown()` 是"温和关闭"——中断空闲线程,但队列中的任务仍会执行完毕。`shutdownNow()` 是"强制关闭"——尝试中断所有正在运行的线程,并将队列中未执行的任务以 `List<Runnable>` 返回。`terminated()` 是一个空的钩子方法,可以通过继承 `ThreadPoolExecutor` 来重写它,用于在线程池终止后做清理工作。
152+
153+
### Worker 工作线程机制
154+
155+
`ThreadPoolExecutor` 将每个工作线程封装为内部类 `Worker``Worker` 继承了 AQS 并实现了 `Runnable` 接口。
156+
157+
**为什么 `Worker` 要继承 AQS?** `Worker` 实现了一个**不可重入的独占锁**,用于配合 `shutdown()` 区分线程是空闲还是正在工作——正在执行任务的 Worker 持有锁,`shutdown()` 对每个 Worker 尝试 `tryLock()`,失败则说明该线程正在工作,不会被中断。
158+
159+
**Worker 的生命周期:**
160+
161+
1. **创建**`execute()` 判断需要新建线程时,调用 `addWorker()` 创建 `Worker` 实例,内部通过 `ThreadFactory` 创建线程。
162+
2. **运行**:线程启动后进入 `runWorker()``while` 循环,通过 `getTask()` 不断从队列取任务执行。核心线程用 `workQueue.take()`(阻塞等待),非核心线程用 `workQueue.poll(keepAliveTime, unit)`(超时等待)。
163+
3. **退出**`getTask()` 返回 `null` 时 Worker 退出循环并清理。返回 `null` 的情况包括:线程池处于停止(`STOP`)状态、线程池处于关闭(`SHUTDOWN`)状态且队列为空、非核心线程等待超时、或运行时缩小了 `maximumPoolSize`。如果退出后工作线程数低于核心数,会自动补充一个新线程。
164+
139165
**`ThreadPoolExecutor` 拒绝策略定义:**
140166

141167
如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时,`ThreadPoolExecutor` 定义一些策略:
@@ -163,6 +189,20 @@ public static class CallerRunsPolicy implements RejectedExecutionHandler {
163189
}
164190
```
165191

192+
### 4 种拒绝策略的实际应用场景
193+
194+
上面介绍了 4 种内置拒绝策略的基本行为,下面结合实际生产经验,说明它们各自适合什么场景:
195+
196+
**`AbortPolicy`**:适用于对任务丢失零容忍的核心业务(如支付、转账)。任务被拒绝时调用方会收到 `RejectedExecutionException`,必须在业务代码中捕获并做补偿(如重试或持久化到数据库后补偿执行)。《阿里巴巴 Java 开发手册》指出,如果不做任何配置,队列满时会直接抛异常,开发者必须显式处理。
197+
198+
**`CallerRunsPolicy`**:适用于不允许丢弃任务、且允许降低提交速度的场景。由于任务在调用者线程中执行,调用者在此期间无法提交新任务,形成了一种天然的**反压(back-pressure)**机制。美团技术团队在《Java 线程池实现原理及其在美团业务中的实践》中提到,这是他们线上业务中较常使用的拒绝策略。但需要注意:如果提交任务的线程是 Web 容器的请求处理线程(如 Tomcat 的 Worker 线程),会导致该请求响应时间显著增加,在延迟敏感的场景中需谨慎。
199+
200+
**`DiscardPolicy`**:适用于任务允许丢失的非关键路径,如日志异步写入、监控指标上报。该策略完全静默(空实现),被拒绝的任务不会留下任何痕迹,排查问题时可能难以发现任务丢失。
201+
202+
**`DiscardOldestPolicy`**:适用于只关心最新数据、旧任务可被覆盖的场景,如实时行情推送、传感器数据采集。需要注意:如果使用了 `PriorityBlockingQueue``poll()` 弹出的是优先级最高的任务而非最旧的任务,可能导致重要任务被误丢。
203+
204+
**生产环境中的常见做法**:以上 4 种内置策略往往不能完全满足需求。Dubbo 框架自定义了 `AbortPolicyWithReport` 策略,在抛异常之外还会将被拒绝的任务信息 dump 到本地文件,方便事后排查。美团技术团队建议对线程池的拒绝次数进行监控和告警。常见的自定义策略思路包括:将被拒绝的任务写入数据库或消息队列后续补偿消费、递增监控计数器上报 Prometheus、或者调用 `workQueue.put(r)` 阻塞等待队列有空位(Netty 中有类似实现)。
205+
166206
### 线程池创建的两种方式
167207

168208
在 Java 中,创建线程池主要有两种方式:
@@ -740,7 +780,7 @@ Exception in thread "main" java.util.concurrent.TimeoutException
740780

741781
#### 为什么不推荐使用`SingleThreadExecutor`
742782

743-
`SingleThreadExecutor``FixedThreadPool` 一样,使用的都是容量为 `Integer.MAX_VALUE``LinkedBlockingQueue`(无界队列)作为线程池的工作队列`SingleThreadExecutor` 使用无界队列作为线程池的工作队列会对线程池带来的影响与 `FixedThreadPool` 相同。说简单点,就是可能会导致 OOM。
783+
`SingleThreadExecutor``FixedThreadPool` 一样,使用的都是容量为 `Integer.MAX_VALUE``LinkedBlockingQueue`(无界队列)。`SingleThreadExecutor` 使用无界队列作为线程池的工作队列会对线程池带来的影响与 `FixedThreadPool` 相同。说简单点,就是可能会导致 OOM。
744784

745785
### CachedThreadPool
746786

0 commit comments

Comments
 (0)