Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >推荐一款工具,辅助估算线程池参数

推荐一款工具,辅助估算线程池参数

作者头像
xiaoxi666
发布于 2022-10-06 06:07:08
发布于 2022-10-06 06:07:08
29600
代码可运行
举报
文章被收录于专栏:xiaoxi666的专栏xiaoxi666的专栏
运行总次数:0
代码可运行

前言

相信接触过并发系统的小伙伴们基本都使用过线程池,或多或少调整过对应的参数。以 Java 中的经典模型来说,能够配置核心线程数、最大线程数、队列容量等等参数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
  this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
       Executors.defaultThreadFactory(), defaultHandler);
}

一般情况下,我们设置参数步骤是:

  1. 确定业务属性,比如IO密集型、CPU密集型、混合型等。
  2. 参考理想化的线程计算模型算出理论值。如《Java并发编程实战》一书中的理想化模型:
  1. 辅之以压测等手段对参数进行逐步调优。
  2. 再高级点,我们也可以对线程池进行监控,并实时对参数进行调整,也即参数动态化方案。可参考:Java线程池实现原理及其在美团业务中的实践

工具推荐

本文则推荐一款工具,它不关心任务内部是如何实现的,而是通过计算运行时的各种系统指标(包括 CPU计算时间、IO等待时间、内存占用等)来直接计算线程池参数的。我们可以直接在这些参数的基础上,再配合压测进行调优,避免盲目调参。

这个工具叫做 dark_magic,直译就是黑魔法,源码参见 https://github.com/sunshanpeng/dark_magic。里面的备注已经很详细,本文不再赘述。只提一下系统指标的计算方式。

指标的计算方式

CPU计算时间 和 IO等待时间 的计算:

  • 先执行两遍任务,进行预热。
  • 获取当前线程的 CPU计算时间,记为 C1
  • 再执行一遍任务
  • 获取当前线程的 CPU计算时间,记为 C2
  • 计算当前任务执行需要的 CPU计算时间:C2 - C1
  • 计算当前任务执行中的 IO等待 时间:总耗时 - CPU计算时间

其中,计算当前线程的 CPU计算时间使用 rt.jar 包中的方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ManagementFactory.getThreadMXBean().getCurrentThreadCpuTime()

内存占用的计算:

  • 生成1000个(可配置)任务加入到阻塞队列中
  • 循环调用 15次(可配置) System.gc() 函数,触发gc
  • 记录目前的内存使用情况,记为 M0
  • 再次生成1000个(可配置)任务加入到阻塞队列中
  • 循环调用 15次(可配置) System.gc() 函数,触发gc
  • 记录目前的内存使用情况,记为 M1
  • 计算当前任务执行需要的内存:M1 - M0

其中,计算内存使用 rt.jar 包中方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()
使用方法

该工具的使用方法也很简单:

  1. 把你的业务代码封装为一个函数,放到 createTask 函数中。
  2. 设定 CPU使用率的期望值、队列占用内存的期望值。
  3. 执行,等待结果输出。

下面分别展示一个CPU密集型和IO密集型的输出(我们设置的 CPU 使用率期望值为 60%,队列占用内存的期望值为 10MB ):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# CPU密集型

Target queue memory usage (bytes): 10240
createTask() produced threadpool.AsyncCPUTask which took 40 bytes in a queue
Formula: 10240 / 40
* Recommended queue capacity (bytes): 256
Number of CPU: 8
Target utilization: 0.59999999999999997779553950749686919152736663818359375
Elapsed time (nanos): 3000000000
Compute time (nanos): 2949786000
Wait time (nanos): 50214000
Formula: 8 * 0.59999999999999997779553950749686919152736663818359375 * (1 + 50214000 / 2949786000)
* Optimal thread count: 4.79999999999999982236431605997495353221893310546875000
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# IO密集型

Target queue memory usage (bytes): 10240
createTask() produced threadpool.AsyncIOTask which took 40 bytes in a queue
Formula: 10240 / 40
* Recommended queue capacity (bytes): 256
Number of CPU: 8
Target utilization: 0.59999999999999997779553950749686919152736663818359375
Elapsed time (nanos): 3000000000
Compute time (nanos): 55528000
Wait time (nanos): 2944472000
Formula: 8 * 0.59999999999999997779553950749686919152736663818359375 * (1 + 2944472000 / 55528000)
* Optimal thread count: 259.19999999999999040767306723864749073982238769531250000

针对线程数的计算而言:

  • 对于 CPU 密集型任务,IO等待时间(Wait time) 远远小于 CPU计算时间(Compute time)。计算出来的推荐核心线程数为 4.8。
  • 对于 IO 密集型任务,IO等待时间(Wait time) 远远大于 CPU计算时间(Compute time)。计算出来的推荐核心线程数为 259。

而队列大小与任务中使用的对象大小有关,这里的内存使用是通过计算 gc 执行前后的内存大小差异得到的(本文中的例子均为 40 B)。由于该算法内部使用 System.gc() 触发 gc。但由于 gc 不一定真的会立刻执行,所以拿到的队列结果可能不一定准确,只能作为粗略参考。

总结

总的来说,dark_magic 这款工具以任务执行时的系统指标数据为基础,计算出比较合理的线程池参数,给我们进行后续的压测调参提供了相对比较合理的参考,值得推荐。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-10-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
如何合理地估算线程池大小?
这个问题虽然看起来很小,却并不那么容易回答。大家如果有更好的方法欢迎赐教,先来一个天真的估算方法:假设要求一个系统的TPS(Transaction Per Second或者Task Per Second)至少为20,然后假设每个Transaction由一个线程完成,继续假设平均每个线程处理一个Transaction的时间为4s。那么问题转化为:
用户1516716
2019/08/30
6520
【高薪程序员必看】万字长文拆解Java并发编程!(9-1):并发工具-线程池
Tomcat分为两个部分:Connector(对外沟通)和Container(实现Servlet规范)
摘星.
2025/05/20
650
【高薪程序员必看】万字长文拆解Java并发编程!(9-1):并发工具-线程池
JUC 多线程 线程池
线程池主要是控制运行线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
万能青年
2019/08/30
6800
JUC 多线程 线程池
线程池:第三章:线程池的手写改造和拒绝策略以及线程池配置合理线程数
我们线程池使用ThreadPoolExecutor的方式进行创建,下面看底层源码:
Java廖志伟
2022/09/28
6460
线程池:第三章:线程池的手写改造和拒绝策略以及线程池配置合理线程数
Java线程池参数配置
在线程池的实际使用中,参数的配置总让人难以把握。在网上搜了一下,主要有以下的方案。跟大家分享。
全栈程序员站长
2022/08/31
1.2K0
如何合理地估算线程池大小
打开cpu使用率 可以看到在线程数量为8的时候,我的这8核机器中的8个cpu全部满负载运行
石臻臻的杂货铺[同名公众号]
2021/07/14
8960
Android实战中的线程池陷阱与源码级优化指南
案例:某电商App在促销期间出现订单处理延迟,线程池配置为核心线程数=CPU核数(8)、最大线程数=16、队列容量=1000。
AntDream
2025/05/13
1400
Android实战中的线程池陷阱与源码级优化指南
Java线程池容量设置
Java中可以通过Executors和ThreadPoolExecutor的方式创建线程池,通过Executors可以快速创建四种常见的线程池,但这种方式在实际使用中并不推荐,因为这种方式创建出来的线程池可控性较差,更推荐的方式是使用ThreadPoolExecutor提供的方法。参考阿里巴巴Java开发规范:
阿杜
2018/08/06
1.1K0
Java线程池容量设置
掌握并行处理:理解并构建自己的线程池
(1)线程使用场景:某类任务特别耗时,会严重影响该线程处理其他任务,因此需要在其他线程异步执行该任务。
Lion 莱恩呀
2024/09/23
1420
掌握并行处理:理解并构建自己的线程池
JUC学习之共享模型之工具上之线程池浅学
ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量
大忽悠爱学习
2022/01/10
4600
JUC学习之共享模型之工具上之线程池浅学
讲真 这次绝对让你轻松学习线程池
老王 是个深耕在帝都的一线码农,辛苦一年挣了点钱,想把钱存储到银行卡里,钱银行卡办理遇到了如下的遭遇
sowhat1412
2020/11/05
4650
(七)线程池的大小如何确定
线程的使用目的是提高运行速度,提高运行的速度是要充分提用CPU和I/O 的利用率。
HaC
2020/12/30
1.9K0
线程池原理(2)
在《阿里巴巴 Java 开发手册》“并发处理”这一章节,明确指出线程资源必须通过线程池提供,不允许在应用中自行显示创建线程。
黑洞代码
2021/03/19
5170
线程池原理(2)
线程池大小的设置策略
线程池需要设置合适的大小,假如设置的太大,线程上线文切换过于频繁,造成大量资源开销,反而会使性能降低。假如设置的太小,存在很多可用的处理器资源却未在工作,会造成资源的浪费和对吞吐量造成损失。
万猫学社
2022/04/22
6140
线程和线程池
多余的空闲线程的存活时间,当前池中线程数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余线程会被销毁直到只剩下corePoolSize
CBeann
2023/12/25
2450
线程和线程池
相关推荐
如何合理地估算线程池大小?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档