当前位置:网站首页>JUC并发编程基础(9)--线程池
JUC并发编程基础(9)--线程池
2022-07-24 05:21:00 【aMythhhhh】
线程池
线程池概述
线程池是一种基于池化思想管理线程的工具,经常出现在多线程服务器上,比如MySQL
池化,顾名思义,是为了最大化收益并最小化风险,而将资源统一在一起管理的一种思想。
由于存在许多的短时间任务,所以线程不断创建和销毁会有额外的代价,另外线程过多就会导致数量膨胀导致过分调度问题,而线程池就能够保证对内核的充分利用,还有一些其它的好处,例如:
- **降低资源消耗:**通过池化技术重复利用已经创建线程,降低线程创建和销毁的代价。
- **提高响应速度:**任务到达的时候,跳过了创建线程的步骤,可以立即执行。
- **提高了线程的可管理性:**线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
- 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。
并发环境下,系统不知道有多少任务需要执行(在任意时刻下),也不知道要给多少资源,就有了很多问题:
- 一直需要申请、销毁资源和调度资源,会带来可能非常巨大的消耗。
- 系统偏偏对这种一直申请资源的情况没有办法,可能会造成资源耗尽。
- 资源申请的多了,你这个线程来一点,那个来一点,就无法管理内部的资源分布,稳定性大大降低。
这时候就需要线程池了,在Java中的体现是ThreadPoolExecutor类。
线程池架构-总体设计
ThreadPoolExecutor类的继承关系为:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uuiDpEzO-1657634311522)(C:\Users\aMyth\AppData\Roaming\Typora\typora-user-images\image-20220712205556165.png)]](/img/d0/10f51aa0bc917f5ca5e00ec23aa0cc.png)
**顶层接口-Executor:**将任务提交和任务执行解耦,用户无需关注如何创建线程,如何调度,只需要提交任务的运行逻辑到Executor中即可。
**接口-ExecutorService:**增加了一些功能,概括为就是,为异步任务生成Future方法,并且提供了管控线程池的方法,比如停止啥的。
**抽象类-AbstractExecutorService:**上层的抽象类,将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法即可。
**实现类-ThreadPoolExecutor:**实现最复杂的运行部分,ThreadPoolExecutor将会一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。
ThreadPoolExecutor运行流程:
任务提交,判断是否立即执行或者拒绝任务,再或者让任务去缓冲队列呆着,就这三种状态。
执行任务的话就申请线程,当任务执行完之后,该线程就会继续获取新任务,当获取不到新任务时,线程回收。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m8ch6trH-1657634311525)(C:\Users\aMyth\AppData\Roaming\Typora\typora-user-images\image-20220712210710153.png)]](/img/59/eaea6ea8a4765e3cc356d655d0d2b7.png)
线程池-生命周期管理
线程池的运行状态和池内线程数量,没有存在两个变量中,而是用一个32位Integer存储的,通过高3位保存运行状态,低29位保存线程数量,可以减少有改动的时候,需要对俩变量进行一直匹配,防止占用锁资源。线程池提供了很多方法去获取当前运行状态、个数,都是基于位运算的方法。
private static int runStateOf(int c) {
return c & ~CAPACITY; } //计算当前运行状态
private static int workerCountOf(int c) {
return c & CAPACITY; } //计算当前线程数量
private static int ctlOf(int rs, int wc) {
return rs | wc; } //通过状态和线程数生成ctl
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//ctl就是保存运行状态和数量的变量,通过ctlof获取,存入该AtomicInteger类型变量

以上就是线程池的五种运行状态,生命周期转换如图所示:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C7URcyMH-1657634311544)(C:\Users\aMyth\AppData\Roaming\Typora\typora-user-images\image-20220712212058896.png)]](/img/08/39c4aa8e1c4c175784c34e510d73d8.png)
线程池-任务执行机制
任务调度
- 首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。
- 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。
- 如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。
- 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。
- 如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。

任务缓冲
阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d45gzVgL-1657634311556)(C:\Users\aMyth\AppData\Roaming\Typora\typora-user-images\image-20220712212840484.png)]](/img/6a/78363d5c79d30f1879a28e7ee87936.png)
具体有哪些阻塞队列,可见上述《阻塞队列》部分。
任务申请
任务的执行有两种可能:一种是任务直接由新创建的线程执行。另一种是线程从任务队列中获取任务然后执行,第一种情况仅出现在线程初始创建的时候,第二种是线程获取任务绝大多数的情况。
线程需要从任务缓存模块中不断地取任务执行,帮助线程从阻塞队列中获取任务,实现线程管理模块和任务管理模块之间的通信。这部分策略由getTask方法实现,其执行流程如下图所示:

任务拒绝
当线程池的任务缓存队列已满,并且线程池中的线程数目达到maximumPoolSize时,就需要拒绝掉该任务,采取任务拒绝策略,保护线程池。
可以通过实现RejectedExecutionHandler接口去自定义拒绝策略,也可以是用JDK提供的拒绝策略。

线程池-Worker线程管理
Worker线程
为了掌握线程的状态并维护线程的生命周期,设计了线程池内的工作线程Worker。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
final Thread thread;//Worker持有的线程
Runnable firstTask;//初始化的任务,可以为null
}
Worker这个工作线程,实现了Runnable接口,并持有一个线程thread,一个初始化的任务firstTask。thread是在调用构造方法时通过ThreadFactory来创建的线程,可以用来执行任务;firstTask用它来保存传入的第一个任务,这个任务可以有也可以为null。如果这个值是非空的,那么线程就会在启动初期立即执行这个任务,也就对应核心线程创建时的情况;如果这个值是null,那么就需要创建一个线程去执行任务列表(workQueue)中的任务,也就是非核心线程的创建。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OncAJXWd-1657634311570)(C:\Users\aMyth\AppData\Roaming\Typora\typora-user-images\image-20220712214507539.png)]](/img/5a/0104141bc4397aa59c36b09ca1c745.png)
Worker线程增加
增加线程是通过线程池中的addWorker方法,该方法的功能就是增加一个线程,该方法不考虑线程池是在哪个阶段增加的该线程,这个分配线程的策略是在上个步骤完成的,该步骤仅仅完成增加线程,并使它运行,最后返回是否成功这个结果。addWorker方法有两个参数:firstTask、core。firstTask参数用于指定新增的线程执行的第一个任务,该参数可以为空;core参数为true表示在新增线程时会判断当前活动线程数是否少于corePoolSize,false表示新增线程前需要判断当前活动线程数是否少于maximumPoolSize,其执行流程如下图所示:

worker线程回收
线程池中线程的销毁依赖JVM自动的回收,线程池做的工作是根据当前线程池的状态维护一定数量的线程引用,防止这部分线程被JVM回收,当线程池决定哪些线程需要回收时,只需要将其引用消除即可。
try {
while (task != null || (task = getTask()) != null) {
//执行任务
}
} finally {
processWorkerExit(w, completedAbruptly);//获取不到任务时,主动回收自己
}
worker执行任务
在Worker类中的run方法调用了runWorker方法来执行任务,runWorker方法的执行过程如下:
1.while循环不断地通过getTask()方法获取任务。 2.getTask()方法从阻塞队列中取任务。 3.如果线程池正在停止,那么要保证当前线程是中断状态,否则要保证当前线程不是中断状态。 4.执行任务。 5.如果getTask结果为null则跳出循环,执行processWorkerExit()方法,销毁线程。
执行流程如下图所示:

参考:https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html
边栏推荐
- 快速打开管理工具的命令
- Positional argument after keyword argument
- synergy局域网实现多主机共享键鼠(amd、arm)
- 在网络中添加SE通道注意力模块
- "Statistical learning methods (2nd Edition)" Li Hang Chapter 16 principal component analysis PCA mind map notes and after-school exercise answers (detailed steps) PCA matrix singular value Chapter 16
- The problem that the user name and password are automatically filled in when Google / Firefox manages the background new account
- UDP通讯应用于多种环境的Demo
- json.dumps()函数解析
- [activiti] gateway
- Loss after cosine annealing decay of learning rate
猜你喜欢

PDF文本合并

Positional argument after keyword argument
![[deep learning] teach you to write](/img/c6/333b16758d79ebd77185be6e3cb38f.png)
[deep learning] teach you to write "handwritten digit recognition neural network" hand in hand, without using any framework, pure numpy

西瓜书/南瓜书--第1,2章总结
![[deep learning] handwritten neural network model preservation](/img/4a/27031f29598564cf585b3af20fe27b.png)
[deep learning] handwritten neural network model preservation
![[MYCAT] Introduction to MYCAT](/img/26/8911fe9e1fb104d7185dda0881804b.png)
[MYCAT] Introduction to MYCAT

Loss after cosine annealing decay of learning rate

STM32 DSP library MDK vc5\vc6 compilation error: 256, (const float64_t *) twiddlecoeff64_ 256, armBitRevIndexTableF64_ 256,
![OSError: [WinError 127] 找不到指定的程序。Error loading “caffe2_detectron_ops.dll“ or one of its dependencies](/img/1d/4c9924c20f697011f0e9cda6616c12.png)
OSError: [WinError 127] 找不到指定的程序。Error loading “caffe2_detectron_ops.dll“ or one of its dependencies

用指针访问二维数组
随机推荐
CRC-16 Modbus代码
数据归一化
Machine learning (Zhou Zhihua) Chapter 3 Notes on learning linear models
【数据库系统原理】第五章 代数和逻辑查询语言:包、扩展操作符、关系逻辑、关系代数与Datalog
删除分类网络预训练权重的的head部分的权重以及修改权重名称
Two architectures of data integration: ELT and ETL
MySql与Qt连接、将数据输出到QT的窗口tableWidget详细过程。
【深度学习】手写神经网络模型保存
Machine learning (zhouzhihua) Chapter 5 notes on neural network learning
[activiti] activiti environment configuration
【USB Host】STM32H7 CubeMX移植带FreeRTOS的USB Host读取U盘,USBH_Process_OS卡死问题,有个值为0xA5A5A5A5
精确计算时间延迟VxWorks 时间戳 详解
JSON. Dumps() function parsing
【树莓派4B】七、远程登录树莓派的方法总结XShell,PuTTY,vncServer,Xrdp
vscode 多行注释总是会自动展开的问题
Numpy cheatsheet
《统计学习方法(第2版)》李航 第17章 潜在语义分析 LSA LSI 思维导图笔记 及 课后习题答案(步骤详细)第十七章
《机器学习》(周志华)第2章 模型选择与评估 笔记 学习心得
DeepSort 总结
"Statistical learning methods (2nd Edition)" Li Hang Chapter 16 principal component analysis PCA mind map notes and after-school exercise answers (detailed steps) PCA matrix singular value Chapter 16