文章目录
-
- 一、先说结论:线程回收核心规则
- [二、回收的本质:getTask() 返回 null](#二、回收的本质:getTask() 返回 null)
- 三、getTask():回收的决策中心
- [四、核心线程 vs 非核心线程的回收逻辑](#四、核心线程 vs 非核心线程的回收逻辑)
-
- [默认情况(allowCoreThreadTimeOut = false)](#默认情况(allowCoreThreadTimeOut = false))
- [开启 allowCoreThreadTimeOut](#开启 allowCoreThreadTimeOut)
- 五、processWorkerExit:退出后的善后
- 六、常见误区
- 线程回收机制全景
- 回答技巧与点评
线程池里那么多线程,什么时候回收?是所有线程都回收,还是只回收一部分?核心线程真的永远不会被回收吗?这些问题不搞清楚,线上线程数暴涨你还不知道为什么。
今天咱们深入源码,把线程池的线程回收机制彻底讲透。
一、先说结论:线程回收核心规则
| 维度 | 说明 |
|---|---|
| 非核心线程 | 空闲超过 keepAliveTime 自动回收 |
| 核心线程 | 默认不回收,即使空闲 |
| allowCoreThreadTimeOut | 设为 true 后,核心线程也参与超时回收 |
| 回收本质 | Worker 的 runWorker() 循环取任务失败,线程自然退出 |
| 回收时机 | getTask() 返回 null → 线程退出 run() → 线程终止 |
一句话记住:非核心线程像临时工,没活就走;核心线程像正式工,默认终身雇佣------除非公司开了 allowCoreThreadTimeOut 这个"裁员开关"。
二、回收的本质:getTask() 返回 null
线程池中每个线程都被包装成 Worker,Worker 的核心逻辑是 runWorker():
java
// ThreadPoolExecutor.Worker(简化)
final void runWorker(Worker w) {
try {
while (task != null || (task = getTask()) != null) { // 👈 关键循环
try {
task.run(); // 执行任务
} finally {
task = null;
}
}
} finally {
processWorkerExit(w, false); // 线程退出清理 👈
}
}
核心逻辑: Worker 线程在一个 while 循环中不断通过 getTask() 获取任务。如果 getTask() 返回 null,循环退出,线程自然终止。
所以"回收线程"的本质就是让 getTask() 返回 null。
三、getTask():回收的决策中心
java
private Runnable getTask() {
boolean timedOut = false;
for (;;) {
int c = ctl.get();
int wc = workerCountOf(c);
// 是否允许超时回收?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; // 👈
// 满足回收条件:线程数超过核心数 且 (超时 或 线程数过多)
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null; // 👈 返回 null,线程退出!
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : // 超时等待 👈
workQueue.take(); // 永久等待(核心线程) 👈
if (r != null)
return r;
timedOut = true; // poll 超时了
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
关键变量 timed:
timed = true:使用 poll(keepAliveTime),超时返回 null → 线程退出timed = false:使用 take(),永久阻塞 → 线程一直等
四、核心线程 vs 非核心线程的回收逻辑
默认情况(allowCoreThreadTimeOut = false)
timed = allowCoreThreadTimeOut || workerCount > corePoolSize
- 核心线程(workerCount ≤ corePoolSize):timed = false → take() 永久等待 → 不回收 👈
- 非核心线程(workerCount > corePoolSize):timed = true → poll(keepAliveTime) → 超时回收 👈
生活类比: 正式工下班后在休息室等(永远等),临时工下班后看表计时,时间到了走人。
开启 allowCoreThreadTimeOut
java
pool.allowCoreThreadTimeOut(true); // 👈 开启核心线程超时回收
此时 timed 始终为 true,所有线程都用 poll(keepAliveTime),空闲超时都回收。
五、processWorkerExit:退出后的善后
线程退出后,processWorkerExit() 负责清理:
java
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 1. 从 workers 集合中移除
workers.remove(w);
// 2. 工作线程数 -1
decrementWorkerCount();
// 3. 尝试终止线程池(如果是最后一个线程)
tryTerminate();
// 4. 如果线程数低于核心数,补充一个新 Worker
int c = ctl.get();
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && !workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return;
}
addWorker(null, false); // 补充 Worker 👈
}
}
注意第 4 步: 如果线程退出后,工作线程数低于 corePoolSize,会自动补充新 Worker!这保证了核心线程数的稳定性。
六、常见误区
误区 1:核心线程永远不会退出
→ 开启 allowCoreThreadTimeOut 后核心线程也会超时退出
误区 2:线程池关闭后线程立刻终止
→ shutdown() 会等所有任务执行完,shutdownNow() 会中断所有线程
误区 3:空闲线程消耗 CPU
→ 空闲核心线程阻塞在 take(),不消耗 CPU,只占内存(约 1MB 栈空间)
线程回收机制全景
线程池线程回收 全景
回收条件
├── 非核心线程:空闲超过 keepAliveTime → poll() 返回 null → 退出
├── 核心线程(默认):take() 永久等待 → 不回收
└── 核心线程(allowCoreThreadTimeOut=true):同样 poll() 超时回收
回收流程
getTask() 返回 null
→ runWorker() 循环退出
→ processWorkerExit() 清理
→ 线程自然终止
自动补偿
线程退出后,若 workerCount < corePoolSize → 自动 addWorker()
内存影响
空闲核心线程阻塞在 take() → 不占 CPU,占 ~1MB 栈内存
口诀:非核心看闹钟,超时就走人,
核心默认等永久,开开关也回收,
getTask 返回 null,线程自然终止,
少了会补人,核心数保得住。
回答技巧与点评
标准回答
线程池通过 Worker 线程循环调用 getTask() 获取任务来工作。非核心线程使用 poll(keepAliveTime) 从队列获取任务,超时返回 null 导致线程退出;核心线程默认使用 take() 永久等待,不会被回收。通过 allowCoreThreadTimeOut(true) 可以让核心线程也参与超时回收。线程退出后会执行 processWorkerExit() 清理,如果线程数低于 corePoolSize 还会自动补充 Worker。
加分回答
- 核心线程的内存开销:空闲核心线程虽然不消耗 CPU,但每个线程约占用 1MB 栈空间。如果 corePoolSize 设得很大且长时间空闲,是内存浪费。开启 allowCoreThreadTimeOut 可以在空闲时回收核心线程,节省内存
- 线程池关闭时的回收:shutdown() 调用后不再接受新任务,但会等已有任务执行完,然后所有线程自然退出(getTask 返回 null)。shutdownNow() 会设置中断标志并尝试中断所有线程,正在执行的任务可能被中断
- 动态调整的影响:运行时调大 corePoolSize 不会立即创建线程,只在下次提交任务时补充;调小 corePoolSize 不会直接回收线程,而是等非核心线程自然超时退出或下次 getTask 时判断回收
面试官点评
这道题考的是你对线程池内部机制的深入理解。能说出"非核心线程超时回收"只是表面,能讲清 getTask() 的 timed 判断逻辑、allowCoreThreadTimeOut 的作用、以及 processWorkerExit 的自动补充机制,才说明你真的看过源码。面试官最想听到的是:你理解回收的"本质"是 getTask() 返回 null 导致循环退出,而不是什么"魔法回收"。
内容有帮助?点赞、收藏、关注三连!评论区等你 💪