线程池最佳核心线程数该如何确定

一、为什么要学习线程池最佳线程数的确定
系统线程池的最佳核心线程、最大线程数的确定,是Java开发人员最常遇到的一个技术难题。

为什么呢?

简单来说,配置得当,你的系统性能就能蹭蹭蹭地提升;

配置不当,不仅浪费资源,还可能拖慢整个系统,甚至引发各种奇怪的bug

合理配置线程池有很多讲究,涉及到各种不同的场景和需求:

有时候,系统是IO密集型的,这时候就需要多一些线程来弥补IO等待时间
有时候,系统是CPU密集型的,这时候就需要控制线程数,以避免过多的上下文切换
有时候,系统是混合型的,需要综合考虑各方面的需求…

在这里,小北会一步步拆解,从理论预估到压测验证,再到线上调整,最后还会结合Nacos和PGA,手把手教你实现动态化的线程池管理。

目标就是让你在不管在面试过程中,还是日常系统开发中,都能够游刃有余的应对。

真的免费,如果你近期准备面试跳槽,建议在cxykk.com在线刷题,涵盖 1万+ 道 Java 面试题,几乎覆盖了所有主流技术面试题、简历模板、算法刷题

二、如何确定系统的最佳线程数?
啥是系统的最佳线程数呢?

简单来说,就是在不浪费系统资源的前提下,让你的应用跑得最快、最稳的那个线程数。

线程数太少,任务处理不过来,系统性能就上不去;线程数太多,又会导致资源争夺,反而拖慢系统。

这就像你家厨房做饭,人少了忙不过来,人多了反而碍事。

确定最佳线程数的基本思路和方法
怎么确定这个“最佳线程数”呢?有没有一套成熟的套路来帮助我们快速确定呢?

答案是当然有:

大体上,可以分为三步:

第一步、理论预估
第二步、压测验证
第三步、监控动态调整

三、确定系统的最佳线程数的3个步骤
3.1、线程数的理论预估(设计阶段)
首先、需要确定我们系统的任务类型。 大体上,可以分为三类:IO密集型、CPU密集型和混合型

IO密集型任务:
比如读写文件、网络通信等。这种任务大部分时间都在等IO操作完成,所以CPU闲着也是闲着。这种情况就需要多开点线程,让CPU能干别的事,不至于浪费。

CPU密集型任务:
比如复杂的计算、数据处理等。这种任务会把CPU忙个不停,线程多了反而会抢资源,导致频繁的上下文切换。一般来说,线程数设定为CPU核心数或者稍微多一点就好。

混合型任务:
既有IO操作又有计算任务。这种情况就要综合考虑,根据具体情况来调配线程数

IO密集型线程池线程数预估
计算公式:线程数 = 核心数 / (1 - 阻塞系数) 为了更好地利用CPU资源,我们可以用这个公式来预估线程数:

线程数 = 核心数 / (1 - 阻塞系数)

假设你的系统有4个CPU核心,IO操作的阻塞系数是0.8。那么线程数应该是多少呢?

线程数 = 4 / (1 - 0.8) = 4 / 0.2 = 20

也就是说,开20个线程差不多就能让CPU一直有事做,效率最高

但是一般情况下我们在设计阶段很难对我们的IO阻塞系统进行预估的,所以这里大家一般情况下都是粗估为CPU核数的2倍+1~2都可以,计算如下:

线程数=4*2+2=10

CPU密集型线程池线程数预估
对于CPU密集型任务,一个比较简单的公式是: 计算公式:线程数 = 核心数 + 1

线程数 = 核心数 + 1

因为这种任务主要耗费CPU资源,通常开多一个线程可以提高点效率,但也不要开太多

假设你的系统有4个CPU核心,那么线程数应该是多少呢?

线程数 = 4 + 1 = 5

也就是说,开5个线程就能比较好地利用CPU资源

混合型线程池线程数预估
混合型任务既有IO操作又有计算任务。这种情况就需要综合考虑两种任务的比例情况,来决定线程数

混合型线程池线程数预估, 参考下面的的公式:

最佳线程数 = ((线程等待时间 + 线程 CPU 时间) / 线程 CPU 时间 ) * CPU 核数

在这里插入图片描述
3.2、线程数的压测验证(测试阶段)
按照上面的线程池理论预估,我们开发好了我们的程序,现在得看看这个数在实际中表现如何。这里就需要用到压测工具。压测就是模拟实际的使用情况,看看我们的系统能不能顶住。

既然压测,那就肯定少不了压测工具,常见的压测工具有:

JMeter:开源、功能强大、上手容易,是做压测的神器。
Apache Bench (ab):轻量级工具,特别适合对单一URL进行高并发测试。
Gatling:相对新一点,但非常强大,特别适合高并发场景。
如何做验证呢?

压测方法:

设置测试场景:根据实际使用情况设置压测场景,比如用户登录、下单等。
逐步增加并发:从低并发开始,逐步增加,观察系统表现。
监控系统指标:记录CPU、内存、响应时间、吞吐量等关键指标。
值得注意的是,在互联网公司中,压测一般是交给专业的测试小伙伴来做的,我们只需要根据他们给出的数据,进行对我们的线程池进行调整,多次验证

CPU使用率:看看CPU是否能充分利用,但也不能过载。
响应时间:确保系统响应时间在可接受范围内。
吞吐量:系统能处理的请求数量,越高越好。
错误率:看看是否有大量错误请求。 通过压测,我们可以发现如果线程数设置得太少,CPU使用率低,响应时间长,吞吐量低;
如果线程数设置得太多,CPU过载,频繁上下文切换导致响应时间变长,错误率增加。

3.3、线程数的线上调整(生产阶段)
测的场景,是有限的。而线上的业务, 是复杂的,多样的。

由于系统运行过程中存在的不确定性,很难一劳永逸地规划一个合理的线程数。

所以,需要进行生产阶段线程数的两个目标:
第一维度:可监控预警
在这里插入图片描述

第二维度:可在线调整
在这里插入图片描述
四、实战:结合Nacos实现动态化线程池架构
优秀的动态化线程池轮子,主要有:

Hippo4J
dynamic-tp
如果线上使用,可以使用这些轮子项目。

但是小北还是想要带大家一起从0到1一起去实战下如何通过nacos配置中心动态调整线程池配置 Nacos 实现动态化线程池的参数在线调整,架构如下
在这里插入图片描述
4.1、Nacos线程池配置
1、使用Nacos作为配置中心,将线程池的核心参数配置在Nacos中

threadpool.coreSize=10
threadpool.maxSize=20
threadpool.queueCapacity=50

4.2、线程池配置和Nacos配置变更监听
在应用中实现监听器,监听Nacos配置的变化

@NacosConfigurationProperties(dataId = "threadpool-config", autoRefreshed = true)
public class ThreadPoolConfig {
    private int coreSize;
    private int maxSize;
    private int queueCapacity;
}

4.3、线程池配置的动态刷新
在监听器中动态更新线程池参数

@Autowired
private ThreadPoolExecutor threadPoolExecutor;

@NacosConfigListener(dataId = "threadpool-config")
public void onChange(String configInfo) {
     Map<String, Object> originMap = null; // 将content转换为LinkedHashMap类型
    try {
        originMap = MAPPER.readValue(configInfo, LinkedHashMap.class);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    if (null == originMap) return;
    Map<Object, Object> result = Maps.newHashMap();
    flatMap(result, originMap, MAIN_PROPERTIES_PREFIX);
    bindDtpProperties(result, dtpProperties);

    DtpExecutor.doRefresh(threadPoolExecutor, dtpProperties);
}

4。4、执行线程池刷新操作

/**
 * 真正执行刷新操作的地方
 */
public static void doRefresh(ThreadPoolExecutor executor, DtpProperties props) {
    // 更新线程池中的【corePoolSize】和【maximumPoolSize】,以props的数量为主

    if (props.getMaximumPoolSize() < executor.getMaximumPoolSize()) {...}

    if (!Objects.equals(executor.getMaximumPoolSize(), props.getMaximumPoolSize())) {
        executor.setMaximumPoolSize(props.getMaximumPoolSize());
    }
    if (!Objects.equals(executor.getCorePoolSize(), props.getCorePoolSize())) {
        executor.setCorePoolSize(props.getCorePoolSize());
    }
    executor.setCorePoolSize(props.getCorePoolSize());

    // 更新阻塞队列中的【capacity】

    val blockingQueue : BlockingQueue<Runnable> = executor.getQueue();
    if (!(blockingQueue instanceof ResizableLinkedBlockingQueue)) {
        log.warn("DynamicTp refresh, the blockingqueue capacity cannot be reset, poolName: {}, queueType {}",
            props.getThreadPoolName(), blockingQueue.getClass().getSimpleName());
        return;
    }
    int capacity = blockingQueue.size() + blockingQueue.remainingCapacity();
    if (!Objects.equals(capacity, props.getQueueCapacity())) {
        ((ResizableLinkedBlockingQueue<Runnable>) blockingQueue).setCapacity(props.getQueueCapacity());
    }
}

4.5、LinkedBlockingQueue 实现resize
LinkedBlockingQueue 不支持 resize, 需要重新定制。自定义可以扩容的 LinkedBlockingQueue ,结构如下:
在这里插入图片描述
这里采用的是读写锁,对capacity 的设置,进行线程安全 保护:
在这里插入图片描述
读写锁的使用如下:
在这里插入图片描述
通过对capacity的安全修改,以达到动态扩展目的。

  • 19
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
根据引用\[1\]和引用\[3\]的内容,最大线程数和核心线程数是线程池中的两个重要参数。最大线程数是指线程池中允许的最大线程数量,而核心线程数是指线程池中保持活动状态的最小线程数量。 对于IO密集型任务,根据引用\[1\]的建议,可以将核心线程设置为CPU核数的两倍,最大线程数可以和核心线程数相同。这样可以充分利用CPU资源,并且通过适当调整队列大小,避免触发最大线程数。 对于CPU密集型任务,根据引用\[1\]的建议,可以将核心线程设置为CPU核数加1,最大线程数可以和核心线程数相同。同样,通过适当调整队列大小,可以避免触发最大线程数。 需要注意的是,当线程池中的线程数量超过核心线程数时,空闲时间超过一定时间的线程会被终止,直到线程池中的线程数量不大于核心线程数为止。这是根据引用\[3\]中的描述。 综上所述,最大线程数和核心线程数的具体配置应根据任务类型和系统资源情况进行调整,以达到最佳性能。 #### 引用[.reference_title] - *1* [线程池核心线程数和最大线程数总结](https://blog.csdn.net/qq_34486648/article/details/123381401)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [java线程池合理设置最大线程数和核心线程数](https://blog.csdn.net/lifulian318/article/details/109000675)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Java线程池核心线程数与最大线程数的区别](https://blog.csdn.net/qq_33323054/article/details/106923732)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

princeAladdin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值