news 2026/5/2 2:05:46

孤舟笔记 并发篇二十二 线程池是如何回收线程的?核心线程和非核心线程的回收逻辑大不相同

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
孤舟笔记 并发篇二十二 线程池是如何回收线程的?核心线程和非核心线程的回收逻辑大不相同

文章目录

    • 一、先说结论:线程回收核心规则
    • 二、回收的本质: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。

加分回答
  1. 核心线程的内存开销:空闲核心线程虽然不消耗 CPU,但每个线程约占用 1MB 栈空间。如果 corePoolSize 设得很大且长时间空闲,是内存浪费。开启 allowCoreThreadTimeOut 可以在空闲时回收核心线程,节省内存
  2. 线程池关闭时的回收:shutdown() 调用后不再接受新任务,但会等已有任务执行完,然后所有线程自然退出(getTask 返回 null)。shutdownNow() 会设置中断标志并尝试中断所有线程,正在执行的任务可能被中断
  3. 动态调整的影响:运行时调大 corePoolSize 不会立即创建线程,只在下次提交任务时补充;调小 corePoolSize 不会直接回收线程,而是等非核心线程自然超时退出或下次 getTask 时判断回收
面试官点评

这道题考的是你对线程池内部机制的深入理解。能说出"非核心线程超时回收"只是表面,能讲清 getTask() 的 timed 判断逻辑、allowCoreThreadTimeOut 的作用、以及 processWorkerExit 的自动补充机制,才说明你真的看过源码。面试官最想听到的是:你理解回收的"本质"是 getTask() 返回 null 导致循环退出,而不是什么"魔法回收"。

原文阅读


内容有帮助?点赞、收藏、关注三连!评论区等你 💪

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/2 2:03:36

OBS虚拟摄像头完全指南:如何在视频会议中使用OBS专业画面

OBS虚拟摄像头完全指南&#xff1a;如何在视频会议中使用OBS专业画面 【免费下载链接】obs-virtual-cam 项目地址: https://gitcode.com/gh_mirrors/obs/obs-virtual-cam OBS虚拟摄像头插件是你将OBS Studio专业视频输出转换为虚拟摄像头的终极解决方案&#xff0c;让你…

作者头像 李华
网站建设 2026/5/2 1:53:17

Webpack vs Vite:一个是“老黄牛”,一个是“猎豹”,你选谁?

你准备搭一个新项目&#xff0c;打开搜索引擎&#xff1a;“Webpack还是Vite&#xff1f;” 答案一半一半&#xff0c;你更懵了。今天我们就来场正面PK&#xff1a;Webpack像头任劳任怨的老黄牛&#xff0c;啥都能干&#xff0c;但起步慢&#xff1b;Vite像只猎豹&#xff0c;瞬…

作者头像 李华
网站建设 2026/5/2 1:45:24

超空间视觉语言模型中的不确定性引导组合对齐

1. 超空间视觉语言模型中的不确定性引导组合对齐视觉语言模型(Vision-Language Models, VLMs)近年来在跨模态理解任务中展现出强大能力&#xff0c;但其欧几里得嵌入空间在处理层次化结构时存在固有局限。想象一下&#xff0c;当你看到一张"海滩日落"的照片时&#x…

作者头像 李华
网站建设 2026/5/2 1:45:23

开源技能管理:构建团队知识资产与高效学习路径

1. 项目概述&#xff1a;当技能成为开源资产最近在整理团队的知识库和新人培训材料时&#xff0c;我一直在思考一个问题&#xff1a;我们如何能更高效地沉淀、复用和迭代那些无形的“技能”与“经验”&#xff1f;一份文档、一个PPT&#xff0c;往往只是知识的静态快照&#xf…

作者头像 李华
网站建设 2026/5/2 1:42:08

基于GitHub Action的AI代码审查工具:Robin AI Reviewer实战指南

1. 项目概述与核心价值 在团队协作开发中&#xff0c;代码审查&#xff08;Code Review&#xff09;是保障代码质量、统一团队规范、促进知识共享的关键环节。然而&#xff0c;随着项目迭代速度加快和团队规模扩大&#xff0c;传统的人工审查模式常常面临瓶颈&#xff1a;资深…

作者头像 李华