详细剖析 Java 动态线程池的扩容以及缩容操作
前言
在项目中,我们经常会使用到线程来处理加快我们的任务。但为了节约资源,大多数程序员都会把线程进行池化,使用线程池来更好的支持我们的业务。
Java 线程池ThreadPoolExecutor
有几个比较核心的参数,如corePoolSize、maximumPoolSize
等等。无论是在工作中还是在面试中,都会被问到,如何正确的设置这几个参数。
线程池的参数并不好配置。一方面线程池的运行机制不是很好理解,配置合理需要强依赖开发人员的个人经验和知识。项目 IO 密集型还是 CPU 密集型等等,总归很难确定一个完美的参数,此时就有了动态线程池的诞生。
动态线程池(DTP)原理
其实动态线程池并不是很高大上的技术,它底层依旧是依赖了ThreadPoolExecutor
的一些核心接口方法。我们通过下面图片可以很清楚的看到,ThreadPoolExecutor
本身就给我们提供了很多钩子方法,让我们去定制化。

那么其原理也非常简单了,我们在运行中假设有一个线程池叫做TaskExecutor
他的核心线程池默认假设是 10,现在我发觉不够用了,此时我想把他的核心线程池调整为 20
我可以写一个远程配置(可以阿波罗,zk,redis 什么都可以)。然后监听到了这个配置变为了 core.pool.size=20
然后我获取到了这个线程池
TaskExecutor
,并且调用setCorePoolSize(20)
,那么这个TaskExecutor
核心线程数就变为了 20
就是这么简单,拨开表面,探究原理,内部其实非常的简单。当时公司里面的线程池还有加一些友好的界面、监控告警、操作日志、权限校验、审核等等,但本质就是监听配置,然后调用 setCorePoolSize 方法去实现的,最大线程数类似。

动态线程池缩容
首先提出几个问题
核心线程数为 5,现在有 3 个线程在执行,并且没有执行完毕,我修改核心线程数为 4,是否修改成功
核心线程数为 5,现在有 3 个线程在执行,并且没有执行完毕,我修改核心线程数为 1,是否修改成功
让我们带着疑问去思考问题。
首先第一个问题,因为核心线程池数为 5,仅有 3 个在执行,我修改为 4,那么因为有 2 个空闲的线程,它只需要销毁 1 个空闲线程即可,因此是成功的
第二个问题,核心线程池数为 5,仅有 3 个在执行,我修改为 1。虽然有 2 个空闲线程,但是我需要销毁 4 个线程。因为有 2 个空闲线程,2 个非空闲线程。我只能销毁 2 个空闲线程,另外 2 个执行的任务不能被打断,也就是执行后仍然为 3 个核心线程数。
那什么时候销毁剩下 2 个执行的线程呢,等到 2 个执行的任务完毕之后,就会销毁它了。假设这个任务是一个死循环,永远不会结束,那么核心线程数永远是 3,永远不能设置为 1
我们举一个代码的例子如下
输出结果为如下
有兴趣的读者可以拿这块带去自己去试试,输出结果里面的注释 我写的非常详细,大家可以详细品品这块输出结果。
动态线程池扩容
扩容我就不提问问题了,和缩容异曲同工,但我希望读者可以先看下以下代码,不要看答案,认为会输出什么结果,看下是否和自己想的是否一样,如果一样,那说明你已经完全懂了,如果不一样,是什么原因。
输出结果为如下 (注意观察输出 queued tasks 的变化!!!)
最后
在业务中,我们为了提高效率使用了线程,为了加快线程我们使用了线程池,而又为了更好的利用线程池的资源,我们又实现了动态化线程池。这也就是遇到问题、探索问题、解决问题的一套思路吧。
我们从底层原理分析,发现动态线程池的底层原理非常简单,希望大家不要恐惧,往往拨开外衣,发现里面最根本的原理,才能是我们更好的捋清楚其中的逻辑。希望本文提供的动态化线程池思路能对大家有帮助。
最终也极力希望读者朋友们,可以将上述两个例子详细分析一下原因,相信会有不小的进步,谢谢大家。
文章转载自:程序员博博
评论