➡️博客首页 https://blog.csdn.net/Java_Yangxiaoyuan
欢迎优秀的你👍点赞、🗂️收藏、加❤️关注哦。
本文章CSDN首发,欢迎转载,要注明出处哦!
先感谢优秀的你能认真的看完本文,有问题欢迎评论区交流,都会认真回复!
图文源码详解什么是线程池、线程池的底层到底是如何实现的
- 一、✅引言
- 1.1✅典型解析
- 1.2✅线程池什么情况下使用
- 1.3✅线程池的优缺点
- 二、✅拓展知识仓
- 2.1✅线程池的实现原理
- 2.2✅添加一个任务
- 2.3✅添加worker线程
- 2.4✅worker线程处理队列任务
- 三、✅总结
- 3.1✅线程池参数
- 3.2✅Executors
- 3.3✅线程池相关
一、✅引言
1.1✅典型解析
线程池是池化技术的一种典型实现,所谓池化技术就是提前保存大量的资源,以备不时之需。在机器资源有限的情况下,使用池化技术可以大大的提高资源的利用率,提升性能等。
线程池,说的就是提前创建好一批线程,然后保存在线程池中,当有任务需要执行的时候,从线程池中选一个线程来执行任务。
在编程领域,比较典型的池化技术有:
线程池、连接池、内存池、对象池等
Java中线程池的继承关系如下:
1.2✅线程池什么情况下使用
线程池适合在以下情况下使用:
1. 有大量短时间任务需要处理。如果每个任务都单独开一个线程,那么线程的创建、销毁与切换会消耗相当多的资源,而线程池可以重用已有的线程,从而减少这些开销。
2. 需要管理多个任务的执行顺序。线程池可以根据任务的优先级或提交时间等因素来调度任务的执行,从而更加稳定和可控。
3. 需要限制并发执行的任务数量。通过设置线程池的最大线程数量,可以保证同时执行的任务数不会超过设定值,从而避免系统资源被过度占用。
4. 需要异步执行任务,并获取任务的执行结果。线程池可以通过返回std::future对象来实现异步执行任务,并在需要时获取任务的执行结果。
5. 并发任务处理:线程池可以用于处理并发的任务,例如处理请求、批量处理数据、并行计算等。通过线程池,可以管理和复用线程,提高任务的执行效率。
6. 异步任务执行:线程池可以用于执行异步任务,将任务提交给线程池后,可以立即返回并继续执行后续代码,不必等待任务完成。适用于需要在后台执行耗时任务,同时不阻塞主线程的场景。
1.3✅线程池的优缺点
线程池是一种用于管理线程的机制,它可以在程序运行时创建一定数量的线程,并将这些线程放入线程池中以供复用。线程池可以有效地减少线程的创建和销毁开销,提高系统的性能和响应速度。下面是一些线程池的优缺点:
优点:
- 降低资源消耗:线程池中的线程可以重复利用,避免了频繁地创建和销毁线程,从而降低了系统的开销。
- 提高响应速度:线程池可以预先创建一定数量的线程,当任务到达时可以直接使用这些线程,无需等待线程的创建,提高了系统的响应速度。
- 提高线程的可管理性:线程池可以进行统一的分配、调优和监控,方便对线程的管理和维护。
- 控制并发数:线程池可以限制任务的并发执行数量,避免了过多的线程导致系统资源的耗尽。
- 提供任务队列:线程池可以配合任务队列使用,缓冲尚未执行的任务,避免了任务因为线程不够而被拒绝执行的情况。
缺点:
- 无法充分利用多核资源:由于线程数量有限,当线程数量过多时,可能会浪费系统资源,并且无法充分利用多核资源。
- 无法适应动态负载变化:线程池中的线程数量是固定的,因此在面对任务负载的动态变化时,线程池可能无法做到自适应调整。
- 无法处理无限制创建线程的情况:虽然线程池可以限制任务的并发数,但是如果程序中出现无限制创建线程的情况,线程池也无法解决该问题。
- 维护成本较高:如果线程池的大小设置不当或者调优不当,可能会导致系统性能的下降或者资源的浪费,需要进行相应的调整和维护。
二、✅拓展知识仓
2.1✅线程池的实现原理
通常,一般构造函数会反映出这个工具或这个对象的数据存储结构。
如果把线程池比作一个公司。公司会有正式员工处理正常业务,如果工作量大的话,会雇佣外包人来工作。
闲时就可以释放外包人员以减少公司管理开销。一个公司因为成本关系,雇佣的人员始终是有最大数。
如果这时候还有任务处理不过来,就走需求池排任务。
- acc: 获取调用上下文
- corePoolSize: 核心线程数量,可以类比正式员工数量,常驻线程数量
- maximumPoolSize: 最大的线程数量,公司最多雇佣员工数量。常驻+临时线程数量
- workQueue: 多余任务等待队列,再多的人都处理不过来了,需要等着,在这个地方等
- keepAliveTime: 非核心线程空闲时间,就是外包人员等了多久,如果还没有活干,解雇了
- threadFactory:创建线程的工厂,在这个地方可以统一处理创建的线程的属性。每个公司对员工的要求不一样,恩,在这里设置员工的属性。
- handler: 线程池拒绝策略,什么意思呢? 就是当任务实在是太多,人也不够,需求池也排满了还有任务咋办?默认是不处理,抛出异常告诉任务提交者,我这忙不过来了
2.2✅添加一个任务
接着,我们看一下线程池中比较重要的execute方法,该方法用于向线程池中添加一个任务。
核心模块用红框标记了:
- 第一个红框: workerCount0f万法根据tl的低29位,得到线程池的当前线程数,如果线程数小于corePoolSize,则执行addWorker方法创建新的线程执行任务。
- 第二个黄框:判断线程池是否在运行,如果在,任务队列是否允许插入,插入成功再次验证线程池是否运行,如果不在运行,移除插入的任务,然后抛出拒绝策略。如果在运行,没有线程了,就启用一个线程。
- 第三个黄框: 如果添加非核心线程失败,就直接拒绝了。
这里逻辑稍微有点复杂,画了个流程图仅供参考:
接下来,我们看看如何添加一个工作线程的?
2.3✅添加worker线程
从方法 execute
的实现可以看出: addWorker
主要负责创建新的线程并执行任务,代码如下(这里代码有点长,没关系,也是分块的,总共有5个关键的代码块) :
- 第一个红框 : 做是否能够添加工作线程条件过滤:
判断线程池的状态,如果线程池的状态值大于或等 SHUTDOWN
,则不处理提交的任务,直接返回;
- 第二个红框:做自旋,更新创建线程数量:
通过参数 core
判断当前需要创建的线程是否为核心线程,如果core为true,且当前线程数小于 corePoolSize
,则跳出循环,开始创建新的线程:
有人或许会疑问 retry 是什么? 这个是iava中的goto语法。只能运用在break和continue后面
接着看后面的代码:
- 第一个红框: 获取线程池主锁。
线程池的工作线程通过Woker类实现,通过ReentrantLock锁保证线程安全。
- 第二个红框: 添加线程到workers中 (线程池中)。
- 第三个红框: 启动新建的线程。
接下来,我们看看workers是什么。
一个hashSet。所以,线程池底层的存储结构其实就是一个HashSet。
2.4✅worker线程处理队列任务
- 第一个红框: 是否是第一次执行任务,或者从队列中可以获取到任务。
- 第二个红框: 获取到任务后,执行任务开始前操作钩子。
- 第三个红框: 执行任务。
- 第四个红框: 执行任务后钩子。
这两人钩子 (beforeExecute,afterExecute
) 允许我们自己继承线程池,做任务执行前后外理。
到这里,源代码分析到此为止。
三、✅总结
3.1✅线程池参数
通过上面的源码解析我们可以看到,线程池的参数主要包括以下七个:
- corePoolSize:线程池中的常驻核心线程数。
- maximumPoolSize:线程池能够容纳同时执行的最大线程数。此值必须大于等于1。
- keepAliveTime:多余的空闲线程存活时间。当空闲时间达到此值时,多余的线程会被销毁直到只剩下corePoolSize个线程为止。
- unit:keepAliveTime的时间单位,如秒、毫秒等。
- workQueue:任务队列,被提交但尚未被执行的任务。当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务。
- threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程,一般用默认的即可。
- handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的数量最大线程数时如何来拒绝请求执行的runnable的策略。
3.2✅Executors
Executors
的创建线程池的方法,创建出来的线程池都实现了ExecutorService
接口。常用方法有以下几个:
newFixedThreadPool(int Threads): 创建固定数目线程的线程池;
newCachedThreadPool(): 创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果没有可用的线程,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60秒钟未被使用的线程。
newSingleThreadExecutor0创建一个单线程化的Executor。
newScheduledThreadPoolint corePoolSize)创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
3.3✅线程池相关
所谓线程池本质是一个hashSet。多余的任务会放在阻塞队列中。
只有当阻塞队列满了后,才会触发非核心线程的创建。所以非核心线程只是临时过来打杂的。直到空闲了,然后自己关闭了。
线程池提供了两个钩子(beforeExecute,afterExecute)
给我们,我们继承线程池,在执行任务前后做一些事情。
线程池原理关键技术: 锁 (lock,cas)
、阻塞队列
、hashSet (资源池)