线程池工作原理深入解析

news2024/11/29 6:38:41

目录

1. 线程正常的生命周期

2. 为什么要用线程池?

3. 线程池的核心原理

4. 怎样创建线程池?

5.线程池的代码实现

6. ThreadPoolExecutor 源码分析

7. ThreadPoolExecutor 工作原理展示(重点)


1. 线程正常的生命周期

我们知道,线程是由生命周期的,在中间不出现阻塞情况下,线程是从一开始的新建状态——>就绪状态——>运行状态——>死亡状态。

2. 为什么要用线程池?

在我们实际开发业务需求时,往往会有很多的用户访问我们的业务,如果来一个用户就创建一个线程,用户结束又销毁线程,如此频繁往复,是非常消耗系统的性能的,因此在实际开发业务时,我们往往会使用线程池。

线程池的好处就是,我们可以在线程池中创建多个线程,当我们有业务需要用到线程时,它会自动到线程池中拿取已经存在线程而不会再去创建新的线程,当线程完成了业务需求后,它会把使用过的线程再还到线程池中而不会销毁它,等待下一次任务的执行,如此一来,就节省了线程的创建与销毁这一动作,提高了程序的运行效率。

3. 线程池的核心原理

(1)我们在初始创建线程池的时候,线程池是空的,当然我们也可以在创建线程池的时候就定义好池子中有几个线程。

(2)当我们需要用到线程时,线程池会去创建新的线程对象,当任务执行完毕之后,它会把线程归还给线程池,下次再有业务需要用到线程时,不会去创建新的线程,直接复用线程池中已经存在的线程。

(3)如果有多个任务都需要使用线程,但是线程池中的线程都正在工作时,而且也无法在创建多余的线程,那么当钱的任务就会进行等待,什么时候有空余线程,任务就会再次开启。

4. 怎样创建线程池?

Java已经帮我们封装好了一个关于线程池的工具类,名叫 Executors,我们可以根据这个工具类去创建不同类型的线程池。这里我把 Executors 的源码中所有创建线程池的方式列举下来如下图

这些以 new 开头的都可以用来创建线程池,每个线程池的类型略有不同,但底层逻辑都差不多。这里我就随便拿两个当例子说一下。

(1)public static ExecutorsService newCachedThreadPool() ,可以创建一个没有线程上限的线程池,从严格意义上来说,它并不算是没有上限,它的上限就是 int 类型的上限,大约是21亿多,我们看它的源码即可得知,这里它的上显示 Integer 的最大值,这里先记住,该构造方法 new 了一个ThreadPoolExecutor,后面会说到。

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

但我们肯定不会去创建这么多的线程,服务器也承受不住的。

(2)public static ExecutorsService newFixedThreadPool(int nThreads),可以创建一个有上限的线程池,上限就是我们传入的参数。

源码如下所示,这里先记住,该构造方法 new 了一个ThreadPoolExecutor,后面会说到。

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

5.线程池的代码实现

测试线程池如何使用,我们先来定义一个线程类

// 实现 Runnable 接口
public class MyRunnable implements Runnable{
    // 重写 run 方法,循环5次
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
// 获取当前线程名称并打印
            System.out.println(Thread.currentThread().getName() + "------" + i);
        }
    }
}

然后再定义一个线程池,给它多添加几个任务

public static void main(String[] args) {
        // 创建一个有上限的线程池,上线设置为3
        ExecutorService pool = Executors.newFixedThreadPool(3);
        // 给线程池添加任务,多添加几个,超出它的上线
// 任务1
        pool.submit(new MyRunnable());
// 任务2
        pool.submit(new MyRunnable());
// 任务3
        pool.submit(new MyRunnable());
// 任务4
        pool.submit(new MyRunnable());
// 任务5
        pool.submit(new MyRunnable());
// 任务6
        pool.submit(new MyRunnable());
    }

运行 main 方法,得到如下结果

可以看到,在控制台中,尽管我们定义了6个线程任务,但线程池中却只有3个线程,分别是pool-1-thread-1,pool-1-thread-2,pool-1-thread-3,这也对应了我们创建线程池时标注的最大线程数量3,而且各位也可以发现,pool-1-thread-2 这个线程打印了两次for循环,其实下面还有线程pool-1-thread-1与线程pool-1-thread-3打印的另外两次for循环,从从我们也可以看出来,虽然我们定义了6个线程任务,但我们定义了一个线程池数量最大为3的线程池,当任务开始时,最先来的三个任务会直接使用线程池中的线程,当第四个第五个第六个任务来到时,它会在线程池的外边排队等待,当前三个任务有人把任务做完将线程归还给线程池后,其他排队等待的线程就可以取出线程池去空余的线程去完成自己的任务了!!!

6. ThreadPoolExecutor 源码分析

刚才我们举得那两个例子,特别用加粗字体说明了这两个线程池底层都是 new 了一个ThreadPoolExecutor,下面我们就来看一下它的源码。

在源码中,有好几个ThreadPoolExecutor 的构造方法,其中有一个最重要的,如下所示,该方法中一共有7个参数,我把它们代表的意义都列举出来。

对于 ThreadPoolExecutor 线程池来讲,我们重点需要理解的有两点,第一点就是这一大堆参数各自代表的含义,第二就是线程池的工作原理

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

int corePoolSize:代表线程池中的核心线程数量;

int maximumPoolSize:代表线程池中最大线程数量;

这里我先解释一下,该线程池中,核心线程永远不会被销毁,除非将线程池销毁,除了核心线程,我们还会有临时线程,就好比是临时工,如果我们线程池的任务过多,核心线程已经忙不过来了,就会有临时线程过来帮忙,当临时线程空闲了一段时间之后没有接到任务,它就又会被销毁。

临时线程 = 最大线程数量 - 核心线程数量

long keepAliveTime:临时线程允许存活的时间数值,如60,100,2000等任意long类型的数都可以;

TimeUnit unit:这个参数与上一个是紧密联系的,代表的是单位,可以为秒,分钟,小时;

如果上面 keepAliveTime 参数数值为60,TimeUnit 单位为Second,则表示表示空余线程可以空闲存活60秒;如果TimeUnit 单位为Minutes,则表示表示空余线程可以空闲存活60分钟,否则就会被销毁。

BlockingQueue<Runnable> workQueue:代表阻塞队列,如果我们的核心线程都在工作时,结果还有任务过来,就会暂时进入到这个阻塞队列中去,可以定义该队列的大小;

ThreadFactory threadFactory:表示我们定义的线程是从哪来的,即创建线程的方式;

RejectedExecutionHandler handler:表示当等待队列满了,临时线程也在工作状态的时候该如何处理,我们可以抛出异常,也可以拒绝服务等等一系列措施。

7. ThreadPoolExecutor 工作原理展示(重点)

刚才我大致说明了 ThreadPoolExecutor 的几个参数代表的意思,下面我来举个例子让大家更好的去理解它

如下所示

现在我定义一个线程池,核心线程数量定义为3,临时线程数量也定义为3,等待队列长度也定义为3,当我们来了10个任务需要线程池去完成时,我来一步步说明,前提是第十个任务来了第一个任务还没有完成,这里我简单描述,各位应该能看懂

(1)第一步,当来了前三个任务,而且是首次接受到任务时,线程池会去创建三个核心线程去处理任务123,如下图所示

(2)第二步,当来了任务456时,因为核心线程都处在工作状态,此时线程456 就会进入到等待队列(也叫阻塞队列)中去排队等待,而不是由临时线程去处理它们;如下图所示

(3)第三步,当来了任务789时,因为此时已经没有空余线程了,等待队列又是满的,此时线程池就会再去创建三个临时线程去处理这任务789;如下图所示

(4)第四步:当提交了第十个任务时,大家可以看到,此时核心线程在工作中,临时线程也在工作中,等待队列也满了,那么此时线程池就会触发拒绝策略;

(5)新来的任务就会被线程池拒绝服务,Java中一共有四种拒绝策略供我们选择,我们可以自行设置,如果不设置默认就是直接将新来的任务丢弃并抛出异常。

此外有一点需要注意,这四种任务拒绝策略都是静态内部类,我们直接通过类名.方法名调用即可。 一般情况下我们也不会去更改拒绝策略,所以其他三种各位同学可以作为了解哦!

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

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

相关文章

Pyinstaller 打包 django 项目如何将命令行参数加入?

起因 Pyinstaller 打包 django 项目&#xff0c;打包成 manage.exe 后用命令行 cmd manage.exe runserver 0.0.0.0:8001 --noreload 来运行感觉很不方便。 希望能够直接把命令行参数也打包进去&#xff0c;直接运行 exe 。我走了些弯路&#xff0c;但最终实现了。 弯路 我看…

Linux —— 基础I/O

一&#xff0c;背景介绍 狭义的文件存放在磁盘上&#xff0c;广义上在Linux下一切皆文件&#xff1b;磁盘上的文件一般为永久存储的外设&#xff0c;本质上对文件的操作&#xff0c;即为对外设的输入和输出&#xff08;简称I/O&#xff09;&#xff1b;空文件并不是不占磁盘文件…

Xamarin.Android实现手写板的功能

目录 1、背景说明2、实现效果3、代码实现3.1 整体思路3.2 核心绘画类-PaintView.cs3.3 对话框类-WritePadDialog.cs3.4 前端实现类-MainActivity3.5 布局文件3.5.1 write_pad.xml3.5.2 activity_main布局文件 4、知识总结5、代码下载6、参考资料 1、背景说明 在实际使用过程中…

【动态规划刷题 6】 买卖股票的最佳时机含冷冻期 买卖股票的最佳时机含手续费

买卖股票的最佳时机含冷冻期 链接: 买卖股票的最佳时机含冷冻期 给定一个整数数组prices&#xff0c;其中第 prices[i] 表示第 i 天的股票价格 。​ 设计一个算法计算出最大利润。在满足以下约束条件下&#xff0c;你可以尽可能地完成更多的交易&#xff08;多次买卖一支股票…

【赠书活动|第四期《互联网广告系统:架构、算法与智能化》】

文章目录 内容简介作者简介读者对象大咖推荐抽奖方式 广告平台的建设和完善是一项长期工程。例如&#xff0c;谷歌早于2003年通过收购Applied Semantics开展Google AdSense 项目&#xff0c;而直到20年后的今天&#xff0c;谷歌展示广告平台仍在持续创新和提升。广告平台是负有…

2024软考系统架构设计师论文写作要点

一、写作注意事项 系统架构设计师的论文题目对于考生来说&#xff0c;是相对较难的题目。一方面&#xff0c;考生需要掌握论文题目中的系统架构设计的专业知识;另一方面&#xff0c;论文的撰写需要结合考生自身的项目经历。因此&#xff0c;如何将自己的项目经历和专业知识有机…

内网穿透:实现公网访问内网群晖NAS的方法

公网远程访问内网群晖NAS 7.X版 【内网穿透】 文章目录 公网远程访问内网群晖NAS 7.X版 【内网穿透】前言1. 在群晖控制面板找到“终端机和SNMP”2. 建立一条连接公网数据隧道3. 获取公网访问内网群晖NAS的数据隧道入口 前言 群晖NAS作为应用较为广泛的小型数据存储中心&#…

RabbitMQ学习——发布订阅/fanout模式 topic模式 rabbitmq回调确认 延迟队列(死信)设计

目录 引出点对点(simple)Work queues 一对多发布订阅/fanout模式以登陆验证码为例pom文件导包application.yml文件rabbitmq的配置生产者生成验证码&#xff0c;发送给交换机消费者消费验证码 topic模式配置类增加配置生产者发送信息进行发送控制台查看 rabbitmq回调确认配置类验…

Shopee虾皮买家号注册时需要注意什么问题

虾皮是一家在线购物平台&#xff0c;如果您打算在虾皮上注册一个买家账号&#xff0c;以下是一些需要注意的问题&#xff1a; 账号安全&#xff1a;确保您选择一个安全的密码&#xff0c;并定期更改密码&#xff0c;以保护您的账号免受未经授权的访问。 个人信息&#xff1a;…

idea报“Could not autowire. No beans of ‘UserMapper‘ type found. ”错解决办法

原因和解决办法 1.原因 idea具有检测功能&#xff0c;接口不能直接创建bean的&#xff0c;需要用动态代理技术来解决。 2.解决办法 1.修改idea的配置 1.点击file,选择setting 2.搜索inspections,找到Spring 3.找到Spring子目录下的Springcore 4.在Springcore的子目录下…

uni-app使用vue语法进行开发注意事项

目录 uni-app 项目目录结构 生命周期 路由 路由跳转 页面栈 条件编译 文本渲染 样式渲染 条件渲染 遍历渲染 事件处理 事件修饰符 uni-app 项目目录结构 组件/标签 使用&#xff08;类似&#xff09;小程序 语法/结构 使用vue 具体项目目录如下&#xff1a; 生命…

三步免费接入 Claude 2.0,支持多账号轮询!

Claude 2.0 已经发布了一段时间&#xff0c;经过我的非暴力测试&#xff0c;比 ChatGPT 3.5 的能力是要强的&#xff0c;有更强大的上下文 100k&#xff0c;相当于 10 万字的上下文记忆,非常适合处理长文档和大的代码段&#xff0c;虽说有些方面略逊色 ChatGPT 4.0 &#xff0c…

日常工具 之 一些 / 方便好用 / 免费 / 在线 / 工具整理

日常工具 之 一些 / 方便好用 / 免费 / 在线 / 工具整理 目录 日常工具 之 一些 / 方便好用 / 免费 / 在线 / 工具整理 1、在线Json &#xff0c;可以在线进行json 格式验证&#xff0c;解析转义等操作 2、Gif动图分解&#xff0c;在线把 gif 图分解成一张张单图 3、在线P…

仓储10、20代电子标签接口文档

标签注册 仓储10代注册 右下角左下角组合按键触发注册 注册成功&#xff1a;右上角绿灯变红灯&#xff0c;并显示信号强度的数值 ​ 仓储20代注册 右下角左下角组合按键触发注册 注册成功&#xff1a;右上角绿灯变红灯&#xff0c;并显示信号强度的数值 ​ 查询电子标签信息…

dinput8.dll导致游戏打不开的解决方法,快速修复dinput8.dll文件

当你尝试启动某个游戏时&#xff0c;如果遇到dinput8.dll文件缺失或损坏的错误提示&#xff0c;可能会导致游戏无法正常运行。dinput8.dll是DirectInput API的一部分&#xff0c;它提供了游戏手柄、键盘和鼠标等输入设备的支持。本文将详细介绍dinput8.dll的作用、导致游戏无法…

核心板如何选择合适的封装?

▍引言 核心板如何选择合适的封装&#xff1f; 核心板是一种集成了CPU、内存、存储、网络等功能的微型计算机模块&#xff0c;可以作为嵌入式系统的核心部件&#xff0c;或者作为开发板的扩展模块。核心板的封装方式决定了它与底板或者开发板的连接方式&#xff0c;影响着核心…

PP-ChatOCR:基于文心大模型的通用图像关键信息抽取利器,开发提效50%!

在日常生活中&#xff0c;大家经常会遇到图像关键信息自动抽取的场景&#xff0c;比如身份证拍照上传自动识别、发票拍照上传自动报销等。 在这个领域&#xff0c;现有的AI技术方案已经能解决一部分需求&#xff0c;但是依然存在一些痛点&#xff0c;比如发票的种类样式极其繁多…

低代码、逻辑、规则、数据分析、协同工具集合,解决企业不同需求

大家好&#xff0c;我是为IT部门兄弟操碎了心的“软件部长”&#xff0c;随着企业IT建设的不断发展&#xff0c;软开企服也在经历了数十年的项目中积累了丰富的经验&#xff0c;为此开始了IT软件的研发之路&#xff0c;之后就一发不可收拾。。。才有了现在出现在市面上的JVS。 …

CVPR 2023 | Attention-Based Point Cloud Edge Sampling

注1:本文系“计算机视觉/三维重建论文速递”系列之一,致力于简洁清晰完整地介绍、解读计算机视觉,特别是三维重建领域最新的顶会/顶刊论文(包括但不限于 CVPR, ICCV, ECCV, NeurIPS等)。本次介绍的论文是: CVPR 2023 | Attention-Based Point Cloud Edge Sampling CVPR 2023 | …

Vue2嵌入HTML页面空白、互相传参、延迟加载等问题解决方案

一、需求分析 最近做的一个用H5加原生开发的html项目&#xff0c;现需要集成到Vue2.0项目里面来。遇到的相关问题做个记录和总结&#xff0c;以便能帮到大家避免踩坑。 二、问题记录 1、页面空白问题 将html页面通过iframe的方式嵌入进来之后&#xff0c;发现页面是空白的&am…