Java线程池如何实现线程复用

news2025/7/18 9:52:59

 线程池把线程和任务进行解耦,线程归线程,任务归任务,摆脱了通过 Thread 创建线程时“一个线程必须对应一个任务”的限制。在线程池中,同一个线程可以从 BlockingQueue 中不断提取新任务来执行,其核心原理在于线程池对 Thread 进行了封装,并不是每次执行任务都会调用 Thread.start() 来创建新线程,而是让每个线程去执行一个“循环任务”,在这个“循环任务”中,不停地检查是否还有任务等待被执行,如果有则直接去执行这个任务,也就是调用任务的 run 方法,把 run 方法当作和普通方法一样的地位去调用,相当于把每个任务的 run() 方法串联了起来,所以线程数量并不增加。

如图是线程池的工作流程图:

    我们可以查看线程池实现的execute()方法源码来进行分析:

public void execute(Runnable command) {   //对Runnable判空    if (command == null)         throw new NullPointerException();  //获取ctl的value值    int c = ctl.get();  //判断线程数量和核心线程数    if (workerCountOf(c) < corePoolSize) {     //为true以核心线程数为限制添加线程        if (addWorker(command, true))             Return;    //更新c的值        c = ctl.get();    }  //检查是否为running并提交到任务队列    if (isRunning(c) && workQueue.offer(command)) {     //更新ctl的值        int recheck = ctl.get();    //检查线程池状态        if (! isRunning(recheck) && remove(command))       reject(command);    //检查是否有线程可用        else if (workerCountOf(recheck) == 0)             addWorker(null, false);    }   //为false以最大线程数为限制添加线程    else if (!addWorker(command, false))         reject(command);}

对照着代码、注释可以看出,完全实现了线程池的工作流程,我们再来分步进行完整的分析与解读。(可以在idea中和ThreadPoolExecutor源码配合一起阅读以下内容)

    在ThreadPoolExecutor源码中,定义了很多“魔法值”,以下三个方法都是根据“魔法值”的含义来实现的。

int c = ctl.get();workerCountOf(c);isRunning(c);
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));//-1000 ... ...0000private static final int COUNT_BITS = Integer.SIZE - 3;      // 29private static final int COUNT_MASK = (1 << COUNT_BITS) - 1; // 00011111 ... ... 11111111
// 状态在高位存储private static final int RUNNING    = -1 << COUNT_BITS;      // 11100000 ... ... 00000000private static final int SHUTDOWN   =  0 << COUNT_BITS;      // 00000000 ... ... 00000000private static final int STOP       =  1 << COUNT_BITS;      // 00100000 ... ... 00000000private static final int TIDYING    =  2 << COUNT_BITS;      // 01000000 ... ... 00000000private static final int TERMINATED =  3 << COUNT_BITS;      // 01100000 ... ... 00000000
private static int ctlOf(int rs, int wc) { return rs | wc; }
private static int workerCountOf(int c) {     return c & CAPACITY; }
private static boolean isRunning(int c) {    return c < SHUTDOWN;}

   来看下ctl的定义:ctl 是一个 AtomicInteger 的类,就是让保存的 int 变量的更新都是原子操作,保证线程安全。ctlOf 方法就是组合运行状态和工作线程数量。可以看到,ctlOf 方法是通过按位或的方式来实现的。这里把一个 int 变量拆成两部分来用。前面3位用来表示状态,后面29位用来表示工作线程数量。所以,工作线程数量最大不能超过 2^29-1 ,ThreadPoolExecutor 的设计者也是考虑不太可能超过这个数,暂时就用了29位。

    workerCountOf方法:CAPACITY的值为00011111 ... ... 11111111,按位与之后便是工作线程数量。

    isRunning方法:初始状态,ctl 的值是11100000 ... … 00000000,表示 RUNNING 状态,和0个工作线程。后面,每创建一个新线程,都把 ctl 加一。在 RUNNING 状态下,ctl 始终是负值,而 SHUTDOWN 是0,所以可以通过直接比较 ctl 的值来确定状态。

思考一下为什么要这样设计?

    使用位运算可以给我们节省很多空间,但是这里就算使用两个值分开表示状态和工作线程数量,也才8个字节,显然不是这个原因。我了解到的可能是为了保证在并发情况下,保证运行状态和工作线程数量的一致性,把两个值放在一个int里面,然后用原子类进行存储和读写,始终可以保持一致性。

    搞清楚这些之后,我们来分步看下execute方法。

①其中首先对Runnable进行判空

    if (command == null)         throw new NullPointerException();

②接下来判断当前线程数是否小于核心线程数,如果小于核心线程数就调用 addWorker() 方法增加一个 Worker,这里的 Worker 就可以理解为一个线程

    if (workerCountOf(c) < corePoolSize) {         if (addWorker(command, true))             return;        c = ctl.get();//这里更新工作线程数量c的值    }

addWorker中的第二个参数如果为true,代表增加线程时判断当前线程是否小于corePoolSize,小于则增加新线程,大于则不增加;为false则代表是否小于maxPoolSize,同理。如果添加线程成功返回true,失败返回false。

③接着往下看,能运行到这个if的话说明线程数大于或等于核心线程数或者addwork失败

    if (isRunning(c) && workQueue.offer(command)) {         int recheck = ctl.get();        if (! isRunning(recheck) && remove(command))             reject(command);        else if (workerCountOf(recheck) == 0)             addWorker(null, false);    } 

那么就需要通过 if (isRunning(c) && workQueue.offer(command)) 检查线程池状态是否为 Running,如果线程池状态是 Running 就把任务放入任务队列中,也就是 workQueue.offer(command)。如果任务可以成功排队,仍然需要仔细检查我们是否应该添加一个线程(因为现有线程可能自上次检查以来已死亡),或者在进入此方法后,线程池可能关闭,需要执行拒绝策略。因此,我们需要重新检查状态。如果线程池已经不处于 Running 状态,说明线程池被关闭,那么就移除刚刚添加到任务队列中的任务,并执行拒绝策略;如果进入 else if 说明前面判断到线程池状态为 Running,那么当任务被添加进来之后就需要防止没有可执行线程的情况发生(比如之前的线程被回收了或意外终止了),所以此时如果检查当前线程数为 0,也就是 workerCountOf(recheck) == 0,那就执行 addWorker() 方法新建线程。

④看最后一个else if

    else if (!addWorker(command, false))         reject(command);

执行到这里,说明线程池不是 Running 状态或线程数大于或等于核心线程数并且任务队列已经满了,根据规则,此时需要添加新线程,直到线程数达到“最大线程数”。所以addWork第二个参数传入false以 maxPoolSize 为上限创建新的 worker;addWorker 方法如果返回 true 代表添加成功,如果返回 false 代表任务添加失败,说明当前线程数已经达到 maxPoolSize,然后执行拒绝策略 reject 方法。如果执行到这里线程池的状态不是 Running,那么addWorker 会失败并返回 false,所以也会执行拒绝策略 reject 方法。

    由以上代码分析得到,在 execute 方法中,多次调用 addWorker 方法把任务传入,addWorker 方法会添加并启动一个 Worker,这里的 Worker 可以理解为是对 Thread 的包装,Worker 内部有一个 Thread 对象,是最终真正执行任务的线程,所以一个 Worker 就对应线程池中的一个线程,addWorker 就代表增加线程。线程复用的逻辑实现主要在 Worker 类中的 run 方法里执行的 runWorker 方法中,简化后的 runWorker 方法代码如下所示。

 runWorker(Worker w) {    Runnable task = w.firstTask;    while (task != null || (task = getTask()) != null) {        try {            task.run();        } finally {            task = null;        }    }}

    可以看出,实现线程复用的逻辑主要在一个不停循环的 while 循环体中。通过取 Worker 的 firstTask 或者通过 getTask 方法从 workQueue 中获取待执行的任务。直接调用 task 的 run 方法来执行具体的任务(而不是新建线程)。在这里,我们找到了最终的实现,通过取 Worker 的 firstTask 或者 getTask方法从 workQueue 中取出了新任务,并直接调用 Runnable 的 run 方法来执行任务,也就是如之前所说的,每个线程都始终在一个大循环中,反复获取任务,然后执行任务,从而实现了线程的复用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/8111.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学生动漫网页设计模板下载你的名字 大学生HTML网页制作作品 简单漫画网页设计成品 dreamweaver学生网站模板

Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业&#xff0c;茶文化网站 | 中华传统文化题材 | 京剧文化水墨风书画 | 中国民间年画文化艺术网站 | HTML期末大学生网页设计作业 HTML&#xff1a;结构 CSS&#xff1a;样式 在操作…

图像增强之灰度变换和直方图均衡化(附代码python+opencv)

一、图像增强的概念和分类 概念&#xff1a;图像增强是采用一系列技术去改善图像的视觉效果&#xff0c;或将图像转换成一种更适合于人或机器进行分析和处理的形式。例如采用一系列技术有选择地突出某些感兴趣的信息&#xff0c;同时抑制一些不需要的信息&#xff0c;提高图像的…

(JVM)双亲委派机制

Java 虚拟机对 class 文件采用的是按需加载的方式&#xff0c;也就是说当需要使用该类时才会将它的 class 文件加载到内存生成 class 对象。而且加载某个类的 class 文件时&#xff0c;Java 虚拟机采用的是双亲委派模式&#xff0c;即把请求交由父类处理&#xff0c;它是一种任…

文档基础模型引领文档智能走向多模态大一统

编者按&#xff1a;自2019年以来&#xff0c;微软亚洲研究院在文档智能领域进行了诸多探索&#xff0c;开发出一系列多模态任务的文档基础模型 (Document Foundation Model)&#xff0c;包括 LayoutLM (v1、v2、v3) 、LayoutXLM、MarkupLM 等。这些模型在诸如表单、收据、发票、…

MySQL中find_in_set函数的使用

1.语法 FIND_IN_SET(str,strlist) &#xff08;1&#xff09;str 要查询的字符串 &#xff08;2&#xff09;strlist 字段名&#xff1b; 参数以”,”分隔 如 (1,2,6,8) 查询字段(strlist)中包含(str)的结果&#xff0c;返回结果为null或记录 假如字符串str在由N个子链组成的…

5G无线技术基础自学系列 | 物理上行控制信道

素材来源&#xff1a;《5G无线网络优化实践》 一边学习一边整理内容&#xff0c;并与大家分享&#xff0c;侵权即删&#xff0c;谢谢支持&#xff01; 附上汇总贴&#xff1a;5G无线技术基础自学系列 | 汇总_COCOgsta的博客-CSDN博客 PUCCH用于传输上行控制信息&#xff08;U…

岭回归、Lasso回归和弹性网络

减少过拟合的一个好方法是对模型进行正则化&#xff08;即约束模型&#xff09;&#xff1a;它拥有的自由度越少&#xff0c;则过拟合数据的难度就越大。正则化多项式模型的一种简单方法是减少多项式的次数。 对于线性模型&#xff0c;正则化通常是通过约束模型的权重来实现的。…

记一次生产中使用CompletableFuture遇到的坑

为什么使用CompletableFuture 业务功能描述&#xff1a;有一个功能是需要调用基础平台接口组装我们需要的数据&#xff0c;在这个功能里面我们要调用多次基础平台的接口&#xff0c;我们的入参是一个id&#xff0c;但是这个id是一个集合。我们都是使用RPC调用&#xff0c;一般…

【22年11月12日更新】搭建宝塔面板、青龙面板“京东代挂”

本文章仅供学习 一、青龙面板是什么&#xff1f; 青龙面板可以运行某东脚本&#xff0c;你在某宝、某度等各个渠道搜索“京东代挂”&#xff0c;都是用青龙面板。 二、搭建宝塔面板 1.更新 yum 包 首先下载finalshell通过账号密码连接服务器&#xff0c;然后输入 yum up…

零基础程序员想要学好.Net,跟着这7个步骤学习就可以了

作为一个初学者程序员&#xff0c;很喜欢问的一个问题就是&#xff1a;零基础如何自学编程&#xff1f;在后台也有很多读者私信我&#xff0c;问我这个问题&#xff0c;其实这个问题比较大&#xff0c;不是一两句就可以说清楚的。 所以&#xff0c;今天结合我个人的经历&#x…

注意力机制详解(Attention详解)

注意力机制与人眼类似&#xff0c;例如我们在火车站看车次信息&#xff0c;我们只关注大屏的车次信息&#xff0c;而忽略大屏外其他内容&#xff0c;从而导致钱包被偷。。。 注意力机制只关注重点信息&#xff0c;忽略不重要的信息&#xff0c;关注最核心的内容。 主要就是这…

推荐系统实战2——EasyRec 推荐框架环境配置

推荐系统实战2——EasyRec 推荐框架环境配置学习前言先验条件EasyRec仓库地址EasyRec环境配置一、EasyRec的下载二、EasyRec的初始化三、EasyRec的安装四、一些额外的情况学习前言 EasyRec是阿里巴巴开源的推荐系统框架。生命苦短&#xff0c;从建好的推荐系统框架开始学&…

【C++】STL简介 -- string 的使用及其模拟实现

文章目录一、STL 简介1、什么是 STL2、STL 的版本3、STL 的六大组件4、STL 的重要性5、如何学习 STL二、string 类的使用1、什么是 string2、string 类模板3、构造函数4、Iterators5、Capacity6、Element Access7、Modify8、String Operations9、Non-member function overloads…

Arduino程序设计(二) 按键控制LED灯

按键控制LED灯程序设计前言一、按键控制LED灯——内部上拉&#xff08;基础&#xff09;二、按键控制LED灯——外部上拉&#xff08;基础&#xff09;三、按键控制LED灯&#xff08;进阶&#xff09;总结参考文献前言 本文主要介绍三种按键控制LED灯的实现方式&#xff0c;分别…

PatchCore原理与代码解读

paper&#xff1a;Towards Total Recall in Industrial Anomaly Detection code&#xff1a;GitHub - amazon-science/patchcore-inspection 存在的问题 目前无监督缺陷检测常用的一种方法是直接利用在ImageNet上预训练的模型中的表示&#xff0c;而不专门进行目标分布的迁…

从零开始将图片信息和空间信息绑定,并在前端展示到地图

作者&#xff1a;xiaoyan 关键词&#xff1a;前端查询时展示和空间数据绑定的图片资源 本文适合零基础入门 背景&#xff1a;iServer支持空间查询&#xff0c;可以将空间数据属性表中的属性查询出来&#xff0c;如通过SQL语句查询出某地大楼实际层高&#xff0c;或者查询出某…

RHCE实验--配置nfs服务

1、开放/nfs/shared目录&#xff0c;供所有用户查询资料&#xff1b; 2、开放/nfs/upload目录&#xff0c;供所有用户上传下载资料&#xff1b; 服务器与客户端都写好yum源以及挂载光盘&#xff0c;然后安装服务包 [rootserver ~]# yum install rpcbind -y [rootserver ~]# y…

Vue3基础

Vue 官网 https://cn.vuejs.org/ https://v3.cn.vuejs.org/ https://staging-cn.vuejs.org/api/ 1、环境 1.1、nodejs node node -vnpm #当前版本 npm -v #升级npm版本 npm install -g npm1.2、vue #安装vue npm install -g vue-cli #安装最新版本 npm install -g vu…

LQ0197 锦标赛【程序填空】

题目来源&#xff1a;蓝桥杯2014初赛 C A组E题 题目描述 本题为代码补全填空题&#xff0c;请将题目中给出的源代码补全&#xff0c;并复制到右侧代码框中&#xff0c;选择对应的编译语言&#xff08;C/Java&#xff09;后进行提交。若题目中给出的源代码语言不唯一&#xff0…

Python简单实现人脸识别检测, 对照片进行评分

大家好&#xff0c;今天和大家说说如何用Python简单实现人脸识别检测, 对照片进行排名&#xff0c;看看自己有多漂亮。 [开发环境]: Python 3.8 Pycharm 2021.2 [模块使用]: requests >>> pip install requeststqdm >>> pip install tqdm 简单实现进度条效果…