文章目录
- 一、先说结论:线程回收核心规则
- 二、回收的本质:getTask() 返回 null
- 三、getTask():回收的决策中心
- 四、核心线程 vs 非核心线程的回收逻辑
- 默认情况(allowCoreThreadTimeOut = false)
- 开启 allowCoreThreadTimeOut
- 五、processWorkerExit:退出后的善后
- 六、常见误区
- 线程回收机制全景
- 回答技巧与点评
- 标准回答
- 加分回答
- 面试官点评
个人网站
线程池里那么多线程,什么时候回收?是所有线程都回收,还是只回收一部分?核心线程真的永远不会被回收吗?这些问题不搞清楚,线上线程数暴涨你还不知道为什么。
今天咱们深入源码,把线程池的线程回收机制彻底讲透。
一、先说结论:线程回收核心规则
| 维度 | 说明 |
|---|---|
| 非核心线程 | 空闲超过 keepAliveTime 自动回收 |
| 核心线程 | 默认不回收,即使空闲 |
| allowCoreThreadTimeOut | 设为 true 后,核心线程也参与超时回收 |
| 回收本质 | Worker 的 runWorker() 循环取任务失败,线程自然退出 |
| 回收时机 | getTask() 返回 null → 线程退出 run() → 线程终止 |
一句话记住:非核心线程像临时工,没活就走;核心线程像正式工,默认终身雇佣——除非公司开了 allowCoreThreadTimeOut 这个"裁员开关"。
二、回收的本质:getTask() 返回 null
线程池中每个线程都被包装成 Worker,Worker 的核心逻辑是 runWorker():
// ThreadPoolExecutor.Worker(简化)finalvoidrunWorker(Workerw){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():回收的决策中心
privateRunnablegetTask(){booleantimedOut=false;for(;;){intc=ctl.get();intwc=workerCountOf(c);// 是否允许超时回收?booleantimed=allowCoreThreadTimeOut||wc>corePoolSize;// 👈// 满足回收条件:线程数超过核心数 且 (超时 或 线程数过多)if((wc>maximumPoolSize||(timed&&timedOut))&&(wc>1||workQueue.isEmpty())){if(compareAndDecrementWorkerCount(c))returnnull;// 👈 返回 null,线程退出!continue;}try{Runnabler=timed?workQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS):// 超时等待 👈workQueue.take();// 永久等待(核心线程) 👈if(r!=null)returnr;timedOut=true;// poll 超时了}catch(InterruptedExceptionretry){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
pool.allowCoreThreadTimeOut(true);// 👈 开启核心线程超时回收此时timed始终为 true,所有线程都用 poll(keepAliveTime),空闲超时都回收。
五、processWorkerExit:退出后的善后
线程退出后,processWorkerExit() 负责清理:
privatevoidprocessWorkerExit(Workerw,booleancompletedAbruptly){// 1. 从 workers 集合中移除workers.remove(w);// 2. 工作线程数 -1decrementWorkerCount();// 3. 尝试终止线程池(如果是最后一个线程)tryTerminate();// 4. 如果线程数低于核心数,补充一个新 Workerintc=ctl.get();if(runStateLessThan(c,STOP)){if(!completedAbruptly){intmin=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 导致循环退出,而不是什么"魔法回收"。
原文阅读
内容有帮助?点赞、收藏、关注三连!评论区等你 💪