01、ThreadPoolExecutor 线程池源码完整剖析 ------ 线程池工作流程、参数解析、简单创建线程池及使用演示

目录

  • 线程池源码剖析
      • 什么是线程?
      • 什么是多线程?
      • 什么是线程池 ?
      • 为什么需要用到线程池 ?
      • 使用线程池有哪些优势 ?
      • 线程的应用场景有哪些 ?
    • 2、线程池工作流程
      • ThreadPoolExecutor参数详解
        • 1、核心线程数(corePoolSize)
        • 2、最大线程数 (maximumPoolSize)
        • 3、非核心线程存活时间 (keepAliveTime)
        • 4、缓存任务的阻塞队列 (BlockingQueue)
        • 5、创建线程的工厂 (ThreadFactory)
        • 6、拒绝策略 (RejectedExecutionHandler)
      • 简单创建和使用线程池
        • 演示:前4个参数
        • 演示:创建线程的工厂 (ThreadFactory) 这个参数
      • 线程池代码分析图
      • 演示拒绝策略
      • 演示关闭线程池
        • shutdown()
        • shutdownNow()
      • 线程池的参数设计分析
        • 核心线程数(corePoolSize)
        • 任务队列长度(workQueue)
        • 最大线程数(maximumPoolSize)
        • 最大空闲时间(keepAliveTime)
      • 总流程分析图

线程池源码剖析


## 1、线程池介绍

什么是线程?

线程(Thread)是计算机中能够执行独立任务的最小单元。线程是进程内的一个执行单位,一个进程可以包含多个线程


什么是多线程?

多线程是指在一个程序中同时执行多个线程的并发编程模型。在多线程模型中,程序被设计为可以同时执行多个任务,每个任务运行在独立的线程中


什么是线程池 ?

线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的任务就是实现了Runnable 或 Callable 接口的实例对象;


为什么需要用到线程池 ?

减少线程的创建。

使用线程池最大的原因就是可以根据系统的需求和硬件环境灵活的控制线程的数量,且可以对所有线程进行统一的管理和控制,从而提高系统的运行效率,降低系统运行压力;


使用线程池有哪些优势 ?

1、线程和任务分离,提升线程重用性;

2、控制线程并发数量,降低服务器压力,统一管理所有线程;

3、提升系统响应速度,
假如创建线程用的时间为T1,执行任务用的时间为T2,销毁线程用的时间为T3,那么使用线程池就免去了 T1 和 T3 的时间;


线程的重用性的解释:
如果我们创建一个线程,然后给它一个任务,那么这个线程执行完之后,就会销毁掉,因为无法让这个线程再执行其他任务,因为这个线程跟这个任务是绑定的,是耦合的,只能做这个任务。
如果线程要重复利用,那么就需要用到线程池,我们直接把任务给线程池,具体由哪个线程来执行,是线程池内部控制的。
在这里插入图片描述


线程的应用场景有哪些 ?

1、并发处理:线程可以实现程序的并发执行,提高程序的运行效率。在服务器端开发中,可以使用多线程来处理多个客户端的请求,提高服务器的并发处理能力
2、云盘文件上传和下载,可以开多线程。
3、多线程批量发送短信邮件
4、框架内部很多都使用多线程提高效率(比如 RocketMQ的多个消息队列)


文件多线程下载解释:
在这里插入图片描述


多线程批量发送短信邮件解释:
比如过节,某公司给众多用户发送节假日的短信祝福,如果是单线程,用户量又大,结果单线程的情况下,短信发到第二天才发完,这就会搞出乌龙。用多线程批量发送能提高效率。


2、线程池工作流程


ThreadPoolExecutor参数详解

ThreadPoolExecutor 是 Java 提供的一个线程池实现类,用于管理和调度线程的执行。

在这里插入图片描述

1、核心线程数(corePoolSize)

表示线程池中的核心线程的数量,也可以称为可闲置的线程数量,
默认情况下,这个线程是不会被回收的,但是也可以通过设置进行回收。

注意点:设置2个核心线程,并不是说先创建出来的那2个线程就是核心线程。

假如有很多任务提交到线程池,线程池创建出线程1和线程2来执行任务,后面阻塞队列满了,根据需要又创建了线程3和线程4,后面当把阻塞队列的任务都执行完了,剩下自身线程的任务在执行,如果线程2和线程3先执行完了,在等待60秒都没有新的任务过来,那么线程2和线程3就会被回收销毁,剩下线程1和线程4。
并不是先创建的线程就是核心线程,而是最后做完任务的那两个线程,因为剩最后两个线程不会被回收,所以也可以当成是核心线程来说。


2、最大线程数 (maximumPoolSize)

当任务比较繁忙,核心线程数在处理任务,其他任务会先被放入阻塞队列中,当阻塞队列任务放满之后,这时候会创建新的线程,创建的线程最多只能达到设置的最大线程数。


3、非核心线程存活时间 (keepAliveTime)

在任务繁忙时,会创建非核心的线程,创建的数量不会超过我们设置的最大线程数。然后这些线程在执行完阻塞队列中的任务后,如果在我们设置的等待时间内,没有新的任务需要处理,那么这些非核心线程就会被销毁掉。
非核心线程没事干,最多能等待的时间,就是这个非核心线程存活时间。


4、缓存任务的阻塞队列 (BlockingQueue)

当核心线程都在处理任务的情况下,新加入的任务会被放到这个阻塞队列中,等待被线程获取。

这个队列的特点是先进先出。


5、创建线程的工厂 (ThreadFactory)

既然是线程池,那自然少不了线程,线程该如何来创建呢?这个任务就交给了线程工厂 ThreadFactory 来完成。

就是线程池内部使用的线程,就是由这个工厂创建出来的。


6、拒绝策略 (RejectedExecutionHandler)

当阻塞队列存放的任务满了,线程数也达到了最大线程数,对新加入的任务执行拒绝策略


策略1:AbortPolicy(默认策略)

丢弃任务 并抛出 RejectedExecutionException 异常。

在这里插入图片描述


策略2:DiscardPolicy
也是丢弃任务,但是不抛出异常。


策略3:DiscardOldestPolicy
丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)


策略4:CallerRunsPolicy
由调用线程处理该任务


简单创建和使用线程池

创建一个线程池,把任务丢给线程池,线程池就会开线程去执行任务。

如图:创建一个线程池,然后执行10个任务。

执行逻辑是:
当前有2个核心线程,这两个核心线程在处理任务时,
如果还有任务过来,这时线程池是不会创建线程的,是先把任务丢到这个缓存任务的阻塞队列里面去等待执行的。
如果这个阻塞队列满了,线程池才会继续创建线程来执行任务。
如果开的线程达到设置的最大线程数了,这时还有任务过来,线程池处理不过来,就会报错:RejectedExecutionException 拒绝执行的异常


演示:前4个参数

线程池的 execute()方法 用于向线程池提交一个任务,该任务将由线程池中的线程来执行。
在execute()方法中,通常会传入一个Runnable对象,该对象表示要执行的任务逻辑。

这个线程池使用到的参数: 核心线程数(corePoolSize)、最大线程数 (maximumPoolSize)、非核心线程存活时间 (keepAliveTime)、缓存任务的阻塞队列 (BlockingQueue)
在这里插入图片描述

缓存任务的阻塞队列,容量为10,最大线程数是4,10+4=14;
加起来这个线程池能接受的最大的执行任务数量就是 14,

如果执行的任务是15次,**超过线程池的最大容量,**就会报错,触发拒绝策略
在这里插入图片描述

如果执行14次任务,在最大限度内,就不会报错。
在这里插入图片描述


演示:创建线程的工厂 (ThreadFactory) 这个参数

没用这个参数的时候,创建的线程是默认的。

用这个参数来创建线程,可以自己定义线程的名字或其他的东西等。
在这里插入图片描述


线程池代码分析图

按这个代码来画图分析
在这里插入图片描述


步骤解析:

1、我们设置的核心线程是2个,这个时候有任务提交到线程池,那么线程池就会创建线程来执行任务。

注意:一开始,没有任务提交到线程池的时候,这个线程池是没有任何线程的。直到提交任务到线程池,线程池才会创建线程来执行任务。

提交第1个任务,那么就创建一个线程1来执行,这时提交第2个任务,如果线程1还没执行完,那么就创建线程2来执行任务。
如果提交第2个任务的时候,线程1已经执行完了,那么该任务就交给线程1 来执行,就不会再创建线程2出来。
在这里插入图片描述

2、如果线程1和线程2还在执行任务,这个时候又来第3个任务提交到线程池。如图

线程池就会把任务先丢到阻塞队列里面去,等这两个核心线程有哪个先执行完自己的任务后,再去阻塞队列里面获取任务来执行。

注意:阻塞队列的特点是先进先出。
在这里插入图片描述


3、如果这个时候,又有11个任务提交到线程池。那么此时一共有14个任务提交到线程池了,如图

如果线程1和线程2 还在执行任务,然后阻塞队列又存满了,那么线程池就会继续创建线程来执行任务,但是创建的线程是不会超过设置的最大线程数的。

在这里插入图片描述

4、如果这个时候,再提交第15个任务到线程池,但是阻塞队列已经存满了,最大线程数4个,也都在执行任务,没有空闲的线程了,达到线程池的最大性能数了,那么就会触发任务的拒绝策略。

在这里插入图片描述


5、如果任务都执行完了,非核心线程在60秒内没有拿到其他任务,那么就会被销毁,就只剩下2个核心线程保留下来。
核心线程是一直存在的,不会被销毁的。
核心线程数、最大线程数、非核心线程数空闲存活时间,都是由我们自己设置的。

注意点:设置2个核心线程,并不是说先创建出来的那2个线程就是核心线程。

假如有很多任务提交到线程池,线程池创建出线程1和线程2来执行任务,后面阻塞队列满了,根据需要又创建了线程3和线程4,后面当把阻塞队列的任务都执行完了,剩下自身线程的任务在执行,如果线程2和线程3先执行完了,在等待60秒都没有新的任务过来,那么线程2和线程3就会被回收销毁,剩下线程1和线程4。
并不是先创建的线程就是核心线程,而是最后做完任务的那两个线程,因为剩最后两个线程不会被回收,所以也可以当成是核心线程来说。
在这里插入图片描述


演示拒绝策略


策略1:AbortPolicy(默认策略)

丢弃任务 并抛出 RejectedExecutionException 异常。

在这里插入图片描述


策略2:DiscardPolicy
也是丢弃任务,但是不抛出异常。
如果,没看到-----执行第【14】个任务,因为这是最后一个任务,直接被丢弃了
在这里插入图片描述


策略3:DiscardOldestPolicy
丢弃阻塞队列最前面的任务,然后重新尝试执行任务(重复此过程)
在这里插入图片描述


策略4:CallerRunsPolicy
由调用线程处理该任务

因为是main这个线程调用了线程池,所以这第14个任务,就交由main线程处理;在main线程空闲的时候,就会处理这个任务。
在这里插入图片描述


演示关闭线程池

在这里插入图片描述


shutdown()

关闭线程池的话,优先考虑这个方法。

线程池的 shutdown()方法 是用于关闭线程池的操作。
关闭线程池:调用这个方法之后,后续添加的任务是不会再被执行了,但是已经加入的任务会继续执行完

如图:在 i==10 之前,已经往线程池加入9个任务了。
调用shutdown()方法后,线程池将不再接受新的任务,并且会等待所有已提交的任务执行完毕。一旦所有任务完成,线程池中的线程将被终止,线程池也将被关闭。
在这里插入图片描述


shutdownNow()

关闭线程池:调用这个方法之后,后续添加的任务是不会再被执行了,但是已经加入的任务,如果还没有开始执行,就不会再去执行了。
停止所有正在执行的任务,暂停处理正在等待的任务,并返回等待执行的任务列表。

shutdownNow() 方法用于立即关闭线程池。与shutdown()方法不同的是,shutdownNow()方法会尝试停止所有正在执行的任务,并且不会等待尚未开始的任务执行完成。
shutdownNow() 方法会尝试通过中断正在执行的线程来停止任务的执行。如果一个线程已经被中断,则会抛出InterruptedException异常。如果线程池中的某个任务无法被中断,则该任务将继续运行直到完成。
在这里插入图片描述

关闭线程池的结果。
已经加入线程池的 2 ~ 9 个任务,可能正在执行,或等待执行,
正在执行的任务如果能被终止,那么就会被终止掉,然后抛出InterruptedException异常。
还没有执行但已经加入线程池的任务,也不会执行。
在这里插入图片描述


线程池的参数设计分析


核心线程数(corePoolSize)

核心线程数的设计需要依据任务的处理时间和每秒产生的任务数量来确定。
例如:一个线程执行一个任务需要0.1秒,1秒就执行10个任务;系统80%的时间每秒都会产生100个任务,那么要想在1秒内处理完这100个任务,就需要10个线程。

此时我们就可以设计核心线程数为10;

当然实际情况不可能这么平均,所以我们一般按照二八原则设计即可,即按照80%的情况设计核心线程数,剩下的20%可以利用最大线程数处理;


任务队列长度(workQueue)

任务队列长度,也就是设计 阻塞队列 能缓存多少个任务。

任务队列长度一般设计为:

核心线程数 / 单个任务执行时间 *2 即可;

例如上面的场景中,核心线程数设计为10,单个任务执行时间为0.1秒,则队列长度可以设计为200 ;


最大线程数(maximumPoolSize)

最大线程数的设计除了需要参照核心线程数的条件外,还需要参照系统每秒产生的最大任务数决定;

例如:上述环境中,如果系统每秒最大产生的任务是1000个,那么:

最大线程数 = ( 最大任务数 - 任务队列长度 ) * 单个任务执行时间;

即: 最大线程数 = (1000 - 200 ) * 0.1 = 80个;


最大空闲时间(keepAliveTime)

这个参数的设计完全参考系统运行环境和硬件压力设定,没有固定的参考值,用户可以根据经验和系统产生任务的时间间隔合理设置一个值即可。


总流程分析图

在这里插入图片描述


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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Jmeter】循环执行某个接口,接口引用的参数变量存在规律变化

变量设置成下面的值即可 ${__V(supplierId_${supplierIdNum})}

【即插即用篇】YOLOv8改进实战 | 引入 Involution(内卷),用于视觉识别的新一代神经网络!涨点神器!

YOLOv8专栏导航:点击此处跳转 前言 YOLOv8 是由 YOLOv5 的发布者 Ultralytics 发布的最新版本的 YOLO。它可用于对象检测、分割、分类任务以及大型数据集的学习,并且可以在包括 CPU 和 GPU 在内的各种硬件上执行。 YOLOv8是一种尖端的、最先进的 (SOTA) 模型,它建立在以前成…

STM32 AI 模型测试

PC仿真软件测试 我在STM32单片机上跑神经网络算法—CUBE-AI_stm32cube.ai-CSDN博客 仿真软件测试结果和真实情况差距过大 云平台测试 Home - STM32Cube.AI Developer Cloud 上传模型文件 点击Start 选择优化方式 可以跳过量化步骤,到Benchmark 选择合适的型号&a…

韵达快递查询入口,一键将退回件筛选出来

批量查询韵达快递单号的物流信息,并将退回件一键筛选出来。 所需工具: 一个【快递批量查询高手】软件 韵达快递单号若干 操作步骤: 步骤1:运行【快递批量查询高手】软件,并登录 步骤2:点击主界面左上角的…

AP9196 DC-DC 升压恒流电源管理芯 200W

产品说明 AP9196是一系列电路简洁的宽调光比升压调光恒流驱动器,适用于3-40V输入电压范围的LED照明领域。AP9196采用我司算法,可以实现高精度的恒流效果,输出电流恒流精度≤3%,电压工作范围为5-40V,可以轻…

浅谈在线监测系统与配电能效平台在供水水厂的应用

贾丽丽 安科瑞电气股份有限公司 上海嘉定 201800 【摘要】针对自来水厂工艺老化资金有限的问题,设计水厂在线监测系统,采用安科瑞,对原水滤后水、出厂水进行采样分析,并通过基于组态的上位机系统实现水质数据的实时监测。该系统…

数据结构和算法-红黑树(定义 性质 查找 插入 删除)

文章目录 红黑树的定义和性质为什么要发明红黑树?红黑树怎么考总览红黑树的定义实例:一颗红黑树练习:是否符合红黑树的要求一种可能的出题思路补充概念:节点黑高 红黑树的性质 红黑树的查找红黑树的插入实例小结与黑高相关的理论 …

YOLOv8涨点技巧:一种新颖的多尺度特征融合iAFF,适配小目标检测

💡💡💡本文全网独家改进:1)引入了一种新颖的多尺度特征融合iAFF;2)为了轻量级部署,和GhostConv有效结合在边缘端具有竞争力的准确性 💡💡💡在YOLOv8中如何使用 1)iAFF加入Neck替代Concat;2)Conv替换为GhostConv;3)加入C3Ghost; 💡💡💡Yolov8魔术…

Java小案例-Java实现人事管理系统

前言 《人事管理系统》该项目采用技术jsp、Struts2、Mybatis、dwr、tomcat服务器、mysql数据库 开发工具eclipse/idea。 【项目使用技术】 Struts2Mybatisdwrjqueryjscss等技术 前端使用技术:JSP, dwr、jquery、js、css等 后端使用技术:Struts2Myba…

Spring事务管理—讲解、案例、应用

简介:Spring事务管理和数据库的事务管理的功能作用上是一样的,在学习数据库时,为了数据完整性,采用了事务管理,即开启事务、提交事务和管理事务。在SpringBoot框架中添加一个注解 Transactional 就可以将当前方法、类和…

C语言之输入输出和字符(2)

目录 缓冲和重定向 ▇缓冲 ▇重定向 字符 转义字符 \和\"……字符和字符" 字符串字面量的写法 字符常量的写法 八进制转义字符和十六进制转义字符 字符编码 在看本节之前,请先看下上一章,做到更好地衔接。https://blog.csdn.net/W061…

为啥 1000 == 1000 是 false

今天跟大家聊一个有趣的话题,在Java中两个Integer对象做比较时,会产生意想不到的结果。 例如: Integer a 100; Integer b 100; System.out.println(ab);其运行结果是:true。 而如果改成下面这样: Integer a 100…