详细剖析Java动态线程池的扩容以及缩容操作

news/2025/3/10 15:05:06/文章来源:https://www.cnblogs.com/wenbochang/p/18688107

前言

在项目中,我们经常会使用到线程来处理加快我们的任务。但为了节约资源,大多数程序员都会把线程进行池化,使用线程池来更好的支持我们的业务。

Java线程池ThreadPoolExecutor有几个比较核心的参数,如corePoolSize、maximumPoolSize等等。无论是在工作中还是在面试中,都会被问到,如何正确的设置这几个参数。

线程池的参数并不好配置。一方面线程池的运行机制不是很好理解,配置合理需要强依赖开发人员的个人经验和知识。项目IO密集型还是CPU密集型等等,总归很难确定一个完美的参数,此时就有了动态线程池的诞生。

动态线程池(DTP)原理

其实动态线程池并不是很高大上的技术,它底层依旧是依赖了ThreadPoolExecutor的一些核心接口方法。我们通过下面图片可以很清楚的看到,ThreadPoolExecutor本身就给我们提供了很多钩子方法,让我们去定制化。

那么其原理也非常简单了,我们在运行中假设有一个线程池叫做TaskExecutor

  1. 他的核心线程池默认假设是10,现在我发觉不够用了,此时我想把他的核心线程池调整为20
  2. 我可以写一个远程配置(可以阿波罗,zk,redis什么都可以)。然后监听到了这个配置变为了core.pool.size=20
  3. 然后我获取到了这个线程池TaskExecutor,并且调用setCorePoolSize(20),那么这个TaskExecutor核心线程数就变为了20

就是这么简单,拨开表面,探究原理,内部其实非常的简单。当时公司里面的线程池还有加一些友好的界面、监控告警、操作日志、权限校验、审核等等,但本质就是监听配置,然后调用setCorePoolSize方法去实现的,最大线程数类似。

public void setCorePoolSize(int corePoolSize) {if (corePoolSize < 0)throw new IllegalArgumentException();int delta = corePoolSize - this.corePoolSize;this.corePoolSize = corePoolSize;if (workerCountOf(ctl.get()) > corePoolSize)interruptIdleWorkers();else if (delta > 0) {int k = Math.min(delta, workQueue.size());while (k-- > 0 && addWorker(null, true)) {if (workQueue.isEmpty())break;}}
}

动态线程池缩容

首先提出几个问题

  1. 核心线程数为5,现在有3个线程在执行,并且没有执行完毕,我修改核心线程数为4,是否修改成功
  2. 核心线程数为5,现在有3个线程在执行,并且没有执行完毕,我修改核心线程数为1,是否修改成功

让我们带着疑问去思考问题。

  1. 首先第一个问题,因为核心线程池数为5,仅有3个在执行,我修改为4,那么因为有2个空闲的线程,它只需要销毁1个空闲线程即可,因此是成功的
  2. 第二个问题,核心线程池数为5,仅有3个在执行,我修改为1。虽然有2个空闲线程,但是我需要销毁4个线程。因为有2个空闲线程,2个非空闲线程。我只能销毁2个空闲线程,另外2个执行的任务不能被打断,也就是执行后仍然为3个核心线程数。
  3. 那什么时候销毁剩下2个执行的线程呢,等到2个执行的任务完毕之后,就会销毁它了。假设这个任务是一个死循环,永远不会结束,那么核心线程数永远是3,永远不能设置为1

我们举一个代码的例子如下

ThreadPoolExecutor es = new ThreadPoolExecutor(5, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
es.prestartAllCoreThreads();  // 预启动所有核心线程// 启动三个任务,执行次数不一样
for (int i = 0; i < 3; i++) {int finalI = i;es.execute(() -> {int cnt = 0;while (true) {try {cnt++;TimeUnit.SECONDS.sleep(2);if (cnt > finalI + 1) {log.info(Thread.currentThread().getName() + " 执行完毕");break;}} catch (InterruptedException e) {e.printStackTrace();}}});
}
TimeUnit.SECONDS.sleep(1);  // 等待线程池中的线程执行
log.info("修改前 es = {}", es);  // 这里核心线程数必定是5es.setCorePoolSize(1);  // 修改核心线程数为1,但是核心线程数为5,并且有3个线程在执行任务,while (true) {TimeUnit.SECONDS.sleep(1); // 等待log.info("修改后 es = {}", es);
}

输出结果为如下

// 修改前核心线程数为5,运行线程数为3
[修改前 es = java.util.concurrent.ThreadPoolExecutor@72d818d1[Running, pool size = 5, active threads = 3, queued tasks = 0, completed tasks = 0]]// 因为有2个空闲线程,先把2个空闲线程给销毁了,剩下3个线程
[修改后 es = java.util.concurrent.ThreadPoolExecutor@72d818d1[Running, pool size = 3, active threads = 3, queued tasks = 0, completed tasks = 0]]// 等第1个任务执行完毕,剩下2个线程
[Main.lambda$d$0:38] [pool-2-thread-1 执行完毕]
[修改后 es = java.util.concurrent.ThreadPoolExecutor@72d818d1[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 1]]// 等第2个任务执行完毕,剩下1个线程
[Main.lambda$d$0:38] [pool-2-thread-2 执行完毕]
[修改后 es = java.util.concurrent.ThreadPoolExecutor@72d818d1[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 2]]// 等第3个任务执行完毕,剩下1个线程。因为我修改的就是1个核心线程
[Main.lambda$d$0:38] [pool-2-thread-3 执行完毕]
[修改后 es = java.util.concurrent.ThreadPoolExecutor@72d818d1[Running, pool size = 1, active threads = 0, queued tasks = 0, completed tasks = 3]]

有兴趣的读者可以拿这块带去自己去试试,输出结果里面的注释 我写的非常详细,大家可以详细品品这块输出结果。

动态线程池扩容

扩容我就不提问问题了,和缩容异曲同工,但我希望读者可以先看下以下代码,不要看答案,认为会输出什么结果,看下是否和自己想的是否一样,如果一样,那说明你已经完全懂了,如果不一样,是什么原因。

// 核心线程数1,最大线程数10
ThreadPoolExecutor es = new ThreadPoolExecutor(1, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
es.prestartAllCoreThreads();  // 预启动所有核心线程for (int i = 0; i < 5; i++) {int finalI = i;es.execute(() -> {int cnt = 0;while (true) {try {cnt++;TimeUnit.SECONDS.sleep(2);if (cnt > finalI + 1) {log.info(Thread.currentThread().getName() + " 执行完毕");break;}} catch (InterruptedException e) {e.printStackTrace();}}});
}TimeUnit.SECONDS.sleep(1);  // 等待线程池中的线程执行
log.info("修改前 es = {}", es);  // 这里核心线程数必定是1, 队列里面有4个任务es.setCorePoolSize(3);  // 修改核心线程数为3while (true) {TimeUnit.SECONDS.sleep(1); // 等待log.info("修改后 es = {}", es);
}   

输出结果为如下 (注意观察输出queued tasks的变化!!!

[修改前 es = java.util.concurrent.ThreadPoolExecutor@1e397ed7[Running, pool size = 1, active threads = 1, queued tasks = 4, completed tasks = 0]]
[修改后 es = java.util.concurrent.ThreadPoolExecutor@1e397ed7[Running, pool size = 3, active threads = 3, queued tasks = 2, completed tasks = 0]][Main.lambda$a$1:73] [pool-2-thread-1 执行完毕]
[修改后 es = java.util.concurrent.ThreadPoolExecutor@1e397ed7[Running, pool size = 3, active threads = 3, queued tasks = 1, completed tasks = 1]][Main.lambda$a$1:73] [pool-2-thread-2 执行完毕]
[修改后 es = java.util.concurrent.ThreadPoolExecutor@1e397ed7[Running, pool size = 3, active threads = 3, queued tasks = 0, completed tasks = 2]][Main.lambda$a$1:73] [pool-2-thread-3 执行完毕]
[修改后 es = java.util.concurrent.ThreadPoolExecutor@1e397ed7[Running, pool size = 3, active threads = 2, queued tasks = 0, completed tasks = 3]][Main.lambda$a$1:73] [pool-2-thread-1 执行完毕]
[修改后 es = java.util.concurrent.ThreadPoolExecutor@1e397ed7[Running, pool size = 3, active threads = 1, queued tasks = 0, completed tasks = 4]][Main.lambda$a$1:73] [pool-2-thread-2 执行完毕]
[修改后 es = java.util.concurrent.ThreadPoolExecutor@1e397ed7[Running, pool size = 3, active threads = 0, queued tasks = 0, completed tasks = 5]]

最后

在业务中,我们为了提高效率使用了线程,为了加快线程我们使用了线程池,而又为了更好的利用线程池的资源,我们又实现了动态化线程池。这也就是遇到问题、探索问题、解决问题的一套思路吧。

我们从底层原理分析,发现动态线程池的底层原理非常简单,希望大家不要恐惧,往往拨开外衣,发现里面最根本的原理,才能是我们更好的捋清楚其中的逻辑。希望本文提供的动态化线程池思路能对大家有帮助。

最终也极力希望读者朋友们,可以将上述两个例子详细分析一下原因,相信会有不小的进步,谢谢大家。

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

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

相关文章

Multiplayer Shooting Game

Launch game in settings添加多人游戏 设置游戏人数:选择网络模式:Play As Listen Server:其中一台有人游玩的机器充当服务器,需要图形渲染 Play As Client:指定一台机器作为服务器,没有人实际在这台机器上游玩游戏,无需图形渲染(大型多人游戏) 配置Project连接到Steam 启…

【Aegisub】卡拉OK模板执行器学习

目录什么是卡拉OK模板执行器卡拉OK模板执行流程概念解析template行code行code区模板修饰语声明类修饰语onceline [name]pre-line [name]sylfurisyl furi其他修饰语allcharfx namefxgroup namemultikeeptagsnoblanknotextrepeat n, loop n内联变量如何使用内联变量变量类型行(…

比特c语言-数组和函数实践:扫雷

目录游戏分析和设计游戏功能说明游戏界面分析和设计数据结构分析文件结构设计游戏设计思路游戏代码实现游戏扩展 游戏分析和设计 游戏功能说明使用控制台实现经典扫雷游戏 游戏可以通过菜单实现是否退出游戏 扫雷棋盘默认为9*9格子 默认随机布置10个雷 可以进行排查雷如果位置不…

ANSYS计算完成后提示:# Error:Result file is incomplete or corrupt. It cannot be read.

问题: ANSYS模态分析计算完成后出现: Error:Result file is incomplete or corrupt. It cannot be read.解决办法: 将安装目录下的:C:\Program Files\ANSYS Inc\v242\Win64\mechanical\v242\commonfiles\Language\en-us\fx0.msb 文件 拷贝到上一级目录:C:\Program Files\…

vue3 输入框中能获取焦点,但是输入不了内容

原因:表单中的ref与model参数是一个,这两个参数应该不同。相同的话,输入不进去内容

让万物「听说」:AI 对话式智能硬件方案和发展洞察

本文整理自声网 SDK 新业务探索组技术负责人,IoT 行业专家 @吴方方 1 月 18 日在 RTE 开发者社区「Voice Agent +硬件分享会」上的分享。本次主要介绍了 AI 对话式智能硬件的发展历程,新一波 AI 浪潮所带来的创新机遇、技术挑战以及未来的展望。在语音交互浪潮的推动下,AIoT…

第一讲C++

第一题Long Loong For a positive integer X, the Dragon String of level X is a string of length (X+3) formed by one L, X occurrences of o, one n, and one g arranged in this order. You are given a positive integer N. Print the Dragon String of level N. Note …

蓝牙芯片HS6621CG-C丰富IO口资源集成红外编码语音采集功能超高性能已移植NFC例程支持语音蓝牙遥控智能门锁智能家居等应用

2.4Ghz的soc蓝牙5.1芯片HS6621CC语音遥控/智能门锁M4F内核兼容NORDIC的2.4G私有协议超低功耗,丰富IO口资源集成红外编码语音采集功能超高性能已移植NFC例程支持语音蓝牙遥控智能门锁智能家居等应用 简介:HS6621CxC 是一种功耗优化的真正的片上系统(SOC)解决方案,既适用于蓝牙…

Leetcode 287. 寻找重复数

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。 示例 1: 输入: [1,3,4,2,2]输出: 2 示例 2:输入: [3,1,3,4,2]输出: 3说明:不能更改原数组(假设数组是只读的)。 只…

性价比PHY6252国产高性能蓝牙芯片支持BLE5.2内置M0内核512KB flash

简介PHY6252是一款支持BLE 5.2功能的系统级芯片(SoC),集成了低功耗的高性能多模射频收发机,搭载32位高性能低功耗处理器,提供64K retention SRAM、可选512/256K Flash、96KB ROM以及256bit efuse,支持基于BLE的安全架构、应用和OTA在线升级。此外,芯片串行外设IO和集成的…

Python代码将大量遥感数据的值缩放指定倍数的方法

本文介绍基于Python中的gdal模块,批量读取大量多波段遥感影像文件,分别对各波段数据加以数值处理,并将所得处理后数据保存为新的遥感影像文件的方法~本文介绍基于Python中的gdal模块,批量读取大量多波段遥感影像文件,分别对各波段数据加以数值处理,并将所得处理后数据保存…

PHY6235超低功耗透传蓝牙soc内置MCU低成本方案

PHY6235是一款用于蓝牙低功耗和专有2.4G应用的系统级芯片(SoC)。它采用高性能、低功耗的32位RISC-V MCU,配备8KB保持型SRAM、80KB ROM以及超低功耗的高性能多模式无线电。此外,PHY6235支持带有安全功能的BLE(蓝牙低功耗)应用。串行外设IO和集成的应用IP使客户产品能够以最…