线程池中的 工作线程如何被回收

2023-11-12

点击“终码一生”,关注,置顶公众号

每日技术干货,第一时间送达!

1、前言

JDK中的ThreadPoolExecutor线程池相信大家都很熟悉,对于线程池的一些高频面试题,比如有哪些参数,每个参数的含义,什么时候发挥作用,工作流程等问题都能回答上来。而对于一些不是很常见的线程池面试题就显得有点模糊,比如:线程池中线程执行完了一个任务接下来是做什么,是等待还是被收回,如果是等待,那么判断的依据是啥,如果是被回收,那么是怎么被回收的。对于这些问题我们就必须深挖ThreadPoolExecutor源码知识了,而不是背几个常见的面试题就行,下面我们一起来看一下线程执行完了一个任务接下来是做什么

2、execute(Runnable command)

execute方法是我们使用线程池的源头,我来看一下调用execute方法后干了什么

重点看addWorker()方法,这里我们要联想到线程池是一个池子,从方法名也可以看出addWorker()增加一个工作者,那么这个Worker对象肯定会放到池子里面去,我们能想到这个池子肯定是一个集合,比如List,Set,Map都可以

果然是用Set做集合,然后会把Worder对象放到Set中,然后我们再来看是怎么向线程池中添加任务的。

我们会看到将传进来的Runnable任务包装成一个Worker对象,然后将Worker的thread成员属性复制给了Thread t局部变量,我们具体看一下Worker这个类

会发现Worker的构造方法会利用创建线程工厂创建一个新线程,并且将当前this对象赋值给了thread成员变量,重写了run()方法,也至于上一张图所指出的thread.start()启动线程会调用Worker对象run()方法,后面重点来看run()方法。

3、runWorker(Worker w)

从源码上可以看到重写的run()方法主要调用的就是runWorker方法

可以看到runWorker方法中,首先会将firstTask赋值给Runnable task变量,firstTask是工作线程第一次跑的时候执行的任务,然后随后就将firstTask=null,即firstTask只会执行一次。接下来就进入while条件循环,如果while条件循环不满足,最后就会进入到finally中的processWorkerExit线程退出,也就从线程池Set中remove线程。所以我们可以把重点关注在while的条件上,第一个条件task != null是用来判断第一次跑的任务,后面的getTask()方法里获取任务,接下来就看getTask()方法,看看getTask()方法什么时候给我们返回null,就代表着这个while条件结束,也就会进入到我们的processWorkerExit()线程退出方法

我们可以看到有两个地方返回null,一个是当线程池的状态是STOP,TIDYING, TERMINATED,或者是SHUTDOWN且工作队列为空,那么就会返回null,decrementWorkerCount()将工作线程数量减1。这里暂时先不考虑线程池中止状态,先假设线程池一直都是RUNNING状态,那么就会进入到第二个返回null的判断条件。第二个地方返回null的条件是:1. 线程池中的工作线程数量 > 最大线程数 or (大于核心线程数 and 工作线程存活时间已经超时) 2. 线程数 > 1 or 队列已经为空 同时满足条件1,2时,再调用CAS扣减线程数,这里解释一下为什么需要用到CAS扣减线程,因为防止两个线程同时满足条件,然后扣减线程数,这样会导致线程数变少。比如核心线程数为4,当前线程数有5个,然后有两个线程判断条件,发现同时满足,则两个线程都会进行扣减,变成3,本来应该保持核心线程数为4的。所以采用CAS操作来扣减线程达到核心线程数,如果扣减成功,则返回null,也就会结束runWorker()方法中的while条件。然后进入finally区域,退出线程

前面分析到第二个地方返回null指的是线程正常执行完一个任务,然后从getTask()获取任务,当获取任务为null时,就会退出线程了,从线程池中删除。下面来分析一下线程池状态为STOP,TIDYING, TERMINATED,或者是SHUTDOWN且工作队列为空时,返回null的情况。

4、调用shutdown(),来终止线程

当调用shutdown()方法时,线程池会等待正在执行任务的线程并且需要将阻塞队列中的任务执行完再进行销毁,并且不再接受新任务。跟shutdownNow()方法不同的是,shutdownNow方法不管是正在执行还是空闲的线程都会进行中断,返回阻塞队列中未完成的任务,阻塞队列中的元素也就不会再执行了

这里可能网友会有疑问了,怎么判断是空闲线程的???

我们可以看到interruptIdleWorkers方法,向线程发出中断信号前,需要获得tryLock()获取独占锁,才能执行t.interrupt()方法,我们再返过头来看runWorker()方法

可以看到如果getTask()返回不为null,则会执行任务,则会拿到独占锁,那么对于正在执行任务的线程是无法发出中断信息号的。除非任务执行完,释放锁。那么interruptIdleWorkers中的w.tryLock()才能拿到锁,发出中断信号

3.1任务刚好执行完任务,释放锁,进入到while条件语句

再次进入到while的判断条件里面去。我们再来看getTask()方法第一个地方返回null的地方

返回null的条件需要两个,一个是shutdown状态,还有一个是队列为空,没有任务(暂时不考虑stop状态)。如果队列中还存在任务,则会调用poll或者take获取任务

这里网友可能会想到线程不是已经是中断状态了吗?那么调用poll和take方法时,应该会一直抛出InterruptedException异常才对。如果对AQS比较熟悉的话,应该可以想到不会出现这种情况,我们再来看poll和task调用的方法

虽然是会抛出中断异常,但是线程中断状态会被重置,也就是下次线程不是中断状态,可以继续获取任务执行,直到队列为空。队列为空,线程池状态又为shutdown状态,最后会返回null。

3.2线程如果本身是阻塞状态

如果线程本身在获取任务是阻塞住了,然后shutdown发出中断信号,抛出中断异常,再次进入到第一个地方判断条件,那么逻辑就跟3.1类似,会判断队列是否是空的,如果是非空的,那么继续会调用poll和take获取任务,首次去获取的时候线程的中断状态也会被重置,下次就能正常的获取任务,直到队列为空,线程池状态为shutdown状态,返回null。

5、总结

总的来说,ThreadPoolExecutor回收线程都是等getTask()获取不到任务,返回null时,调用processWorkerExit方法从Set集合中remove掉线程,getTask()返回null又分为2两种场景:

1. 线程正常执行完任务,并且已经等到超过keepAliveTime时间,大于核心线程数,那么会返回null,结束外层的runWorker中的while循环

2. 当调用shutdown()方法,会将线程池状态置为shutdown,并且需要等待正在执行的任务执行完,阻塞队列中的任务执行完才能返回null

在工作中,一直认为编程代码不是最重要的,重要的是在工作中所养成的编程思维。

PS:防止找不到本篇文章,可以收藏点赞,方便翻阅查找哦

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

线程池中的 工作线程如何被回收 的相关文章

随机推荐

  • Vue 引入高德地图 vue-amap

    一 在高德开发平台 获取Key 已有可跳过 高德开发者平台 链接地址 1 控制台 我的应用 创建应用 添加key 创建应用 新建应用 选择web端 JS平台 last 到这里的 key 就有了 还得到了一个安全密钥 二 引入vue amap
  • 5G应用标志着移动互联网新时代真正到来

    5G网络普及后很可能实现已经多次被我们所畅想的万物互联 通过网络把家用电视 冰箱 洗衣机 空调等连接起来 再通过一个软件来进行控制 5G超高的网速 超低的延迟将会使无人驾驶 无人机作业 远程医疗技术等步入成熟阶段 因此 5G的应用标志着移动
  • TypeScript 在 vue 中的使用

    本文主要介绍 TypeScript 在 vue 中的使用 还有一些j注释起来的 js 代码做对照 参考链接 合成 API 的 TypeScript vue3中配合使用TS 还需要额外安装一个vscode插件 Typescript Vue P
  • 基于snooplog分析蓝牙连接过程,进一步学习蓝牙协议栈

    一 什么是BT snoop log 首先问题 1 为什么远端发来的消息没有收到 2 为什么搜索不到设备 3 为什么连不上 4 总之 研发过程中会遇到很多奇奇怪怪的问题 我们无法通过现象去分析原因 也不可能再过一遍代码吧 这时候需要再某些代码
  • 期货交易,一些你不得不知道的技巧

    众所周知 期货市场变化莫测 既有风险 又兼具了高回报 因此也被誉为 21世纪冒险者的游戏 虽然期货投资受市场 政治形势 经济发展等因素的影响 但作为一个成熟的交易模式 期货依然有着很大的魅力 既可以做长线 也可以短线为王 但无论采用何种交易
  • 如何建设水利数字孪生流域

    数字孪生流域是以物理流域为单元 时空数据为底座 数学模型为核心 水利知识为驱动 对物理流域全要素和水利治理管理活动全过程的数字化映射 智能化模拟 实现与物理流域同步仿真运行 虚实交互 迭代优化 建设水利数字孪生流域 重点包括以下几件事 获取
  • nginx日志access.log error.log按天生成存储,定时删除日志

    bin bash function cut nginx log files for lnmp v0 5 and v0 6 author http lnmp org set the path to nginx log files log fi
  • Qt多线程基础(一)线程同步之互斥锁同步

    一 直接使用QMutex进行同步 创建线程方法 继承自QThread 重写void run 函数 调用成员start 启动线程 start 中可加入优先级参数 互斥锁同步方法 void run 函数中使用QMutex来实现同步 当多个线程访
  • 【JS随笔】监听DOM内容改变

    目录 前言 一 addEventListener 1 示例 二 MutationObserver 1 示例1 2 示例2 总结 参考资料 前言 今天项目遇到一个需要监听DOM变化 根据DOM的变化情况 来展示不同的内容 于是上网搜索了各种方
  • HTML语法学习以及作业解答

    文章目录 技巧 html标签练习 作业01 作业02 作业03 作业04 作业05 作业06 作业07 作业08 作业09 作业10 技巧 1 输入 html5即可补全出html的骨架 或者是 tab键 2 MDN网站 https deve
  • git pull时报合并冲突的bug

    想在本地拉下最新的代码 不知为啥 出现合并冲突的bug warning Cannot merge binary files by tar HEAD vs 050fecfd1537b03953d219429f3 Auto merging by
  • 【解决】linux虚拟机开放端口号,虚拟机centos7开放端口

    原文链接 1 先查看防火墙是否开启的状态 以及开放端口的情况 systemctl status firewalld service 查看防火墙开启还是关闭 sudo firewall cmd list all 可以查看端口开放情况 fire
  • SSRF-服务端请求伪造

    SSRF 服务端请求伪造 1 SSRF是什么 SSRF Server side Request Forge 服务端请求伪造 由攻击者构造的攻击链接传给服务端执行造成的漏洞 一般用来在外网探测或攻击内网服务 2 SSRF可以做什么 扫内网 向
  • Latex编号

    用圆点标号 begin itemize item Latex 1 item Latex 2 item Latex 3 end itemize 效果如下 用数字编号 默认数字编号 begin enumerate item Latex 1 it
  • js 比较两个数组的内容是否相同

    js比较数组 arrA 和 arrB 可以用 arrA sort toString arrB sort toString 来进行比较 用循环肯定可以实现 不过太麻烦 这是一种简单的方法先将数组排序 然后转化成字符串 再比较字符串即可 测试时
  • Linux(ubuntu)mysql数据库主从同步、配置

    一 查看两台主机的版本Ubuntu 16 04 3 root iZ2zeht3zvxbq5ycy698pwZ lsb release a LSB Version core 9 20160110ubuntu0 2 amd64 core 9 2
  • 网站性能优化从入门到粗通(PHP 篇)

    本文来自作者 蒋岩 在 GitChat 上分享 网站性能优化从入门到粗通 PHP 篇 阅读原文 查看交流实录 文末高能 编辑 哈比 题外话 本次 Chat 的主题是入门到粗通 所以内容对于老司机来说会比较 Low 请多包涵 进入正题 一 测
  • Flink学习1——运行时架构(standalone模式)

    本篇主要讲述Flink Standalone模式下的运行时架构以及各个组件负责的功能 Flink的运行方式有很多 但都大同小异 本文基本可以满足对flink运行时架构的学习 正文 Flink系统是主从模式 主要有两个组件构成分别是JobMa
  • 记一道CTF 中遇到的SQL注入新型万能密码问题

    0x00 引言 我们平时遇到的SQL注入万能密码都是形如admin or 1 1 这种使用or 关键字使得查询结果永真 或者形如 UNION Select 1 1 1 FROM admin Where 这种使用union使得查询结果永真 但
  • 线程池中的 工作线程如何被回收

    点击 终码一生 关注 置顶公众号 每日技术干货 第一时间送达 1 前言 JDK中的ThreadPoolExecutor线程池相信大家都很熟悉 对于线程池的一些高频面试题 比如有哪些参数 每个参数的含义 什么时候发挥作用 工作流程等问题都能回