线程池参数如何设置?

前言

着计算机行业的飞速发展,摩尔定律逐渐失效,多核CPU成为主流。使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器。J.U.C提供的线程池:ThreadPoolExecutor类,帮助开发人员管理线程并方便地执行并行任务。了解并合理使用线程池,是一个开发人员必修的基本功。线程池参数配置方案显得十分重要。

一、参数设置的传统方案

1. 线程池中执行的任务性质。

计算密集型的任务比较占cpu,所以一般线程数设置的大小 等于或者略微大于 cpu的核数;但IO型任务主要时间消耗在 IO等待上,cpu压力并不大,所以线程数一般设置较大,例如 多线程访问数据库,数据库有128个表,可能就直接考虑使用128个线程。

2. CPU使用率。

当线程数设置较大时,会有如下几个问题:第一,线程的初始化,切换,销毁等操作会消耗不小的cpu资源,使得cpu利用率一直维持在较高水平。第二,线程数较大时,任务会短时间迅速执行,任务的集中执行也会给cpu造成较大的压力。第三, 任务的集中支持,会让cpu的使用率呈现锯齿状,即短时间内cpu飙高,然后迅速下降至闲置状态,cpu使用的不合理,应该减小线程数,让任务在队列等待,使得cpu的使用率应该持续稳定在一个合理,平均的数值范围。所以cpu在够用时,不宜过大,不是越大越好。可以通过上线后,观察机器的cpu使用率和cpu负载两个参数来判断线程数是否合理。可通过命令查看cpu使用率是否主要花在线程切换上。cpu负载是正在执行的线程和等待执行的线程之和,注意这个等待不是指线程的wait那种等待,而是指线程处于running状态但是还没有被cpu调度的等待,负载较高,意味着cpu竞争激烈,进而说明线程设置较大,在抢cpu资源。负载的值一般约等于 cpu核数 是比较合理的数值。

3. 内存使用率。

线程数过多和 队列的大小都会影响此项数据,队列的大小应该通过前期计算线程池任务的条数,来合理的设置队列的大小,不宜过小,让其不会溢出,因为溢出会走拒绝策略,多少会影响性能,也会增加复杂度,因为你得好好考量你的拒绝策略的选择,拒绝策略包括 AbortPolicy(抛异常), CallerRunsPolicy(主线程执行) 和 DiscardPolicy(丢弃)。也不宜多大,过大用不上,还会消耗较大的内存。

4. 下游系统抗并发的能力。

多线程给下游系统造成的并发等于你设置的线程数,例如如果是多线程访问数据库,你就等考虑数据库的连接池大小设置,数据库并发太多影响其qps,会把数据库打挂等问题。 如果访问的是下游系统的接口,你就得考虑下游系统是否能抗的住这么多并发量,不能把下游系统打挂了。
在这里插入图片描述
调研了以上业界方案后,我们并没有得出通用的线程池计算方式。并发任务的执行情况和任务类型相关,IO密集型和CPU密集型的任务运行起来的情况差异非常大,但这种占比是较难合理预估的,这导致很难有一个简单有效的通用公式帮我们直接计算出结果。

二、线程池参数动态化

尽管经过谨慎的评估,仍然不能够保证一次计算出来合适的参数,那么我们是否可以将修改线程池参数的成本降下来,这样至少可以发生故障的时候可以快速调整从而缩短故障恢复的时间呢?基于这个思考,我们是否可以将线程池的参数从代码中迁移到分布式配置中心上,实现线程池参数可动态配置和即时生效。

1.整体设计

动态化线程池的核心设计包括以下三个方面:

(1)简化线程池配置:线程池构造参数有8个,但是最核心的是3个:corePoolSize、maximumPoolSize,workQueue,它们最大程度地决定了线程池的任务分配和线程分配策略。考虑到在实际应用中我们获取并发性的场景主要是两种:
(1)并行执行子任务,提高响应速度。这种情况下,应该使用同步队列,没有什么任务应该被缓存下来,而是应该立即执行。
(2)并行执行大批次任务,提升吞吐量。这种情况下,应该使用有界队列,使用队列去缓冲大批量的任务,队列容量必须声明,防止任务无限制堆积。所以线程池只需要提供这三个关键参数的配置,并且提供两种队列的选择,就可以满足绝大多数的业务需求。

(2)参数可动态修改:为了解决参数不好配,修改参数成本高等问题。在Java线程池留有高扩展性的基础上,封装线程池,允许线程池监听同步外部的消息,根据消息进行修改配置。将线程池的配置放置在平台侧,允许开发同学简单的查看、修改线程池配置。
(3)增加线程池监控:对某事物缺乏状态的观测,就对其改进无从下手,在线程池执行任务的生命周期添加监控能力,帮助开发同学了解线程池状态。
流程图大概是这样:
在这里插入图片描述

2.功能架构

动态调参:支持线程池参数动态调整、界面化操作;包括修改线程池核心大小、最大核心大小、队列长度等;参数修改后及时生效。
任务监控:支持应用粒度、线程池粒度、任务粒度的Transaction监控;可以看到线程池的任务执行情况、最大任务执行时间、平均任务执行时间、95/99线等。
负载告警:线程池队列任务积压到一定值的时候会通过通讯工具,告知应用开发负责人;当线程池负载数达到一定阈值的时候会通过通讯工具告知应用开发负责人。
操作监控:创建/修改和删除线程池都会通知到应用的开发负责人。
操作日志:可以查看线程池参数的修改记录,谁在什么时候修改了线程池参数、修改前的参数值是什么。
权限校验:只有应用开发负责人才能够修改应用的线程池参数。
架构图:
在这里插入图片描述
参考自美团技术团队。

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java提供了一些方法可以动态修改线程池参数,下面分别介绍: 1. setCorePoolSize(int corePoolSize):设置线程池的核心线程数。该方法可以动态修改线程池的核心线程数,即最小线程数。如果当前线程池中的线程数小于新设置的核心线程数,则会创建新的线程。如果当前线程池中的线程数大于新设置的核心线程数,则不会立即销毁多余的线程,而是等待空闲超时后再销毁。 2. setMaximumPoolSize(int maximumPoolSize):设置线程池的最大线程数。该方法可以动态修改线程池的最大线程数。如果当前线程池中的线程数小于新设置的最大线程数,且任务队列已满,则会创建新的线程执行任务。如果当前线程池中的线程数大于新设置的最大线程数,则不会创建新的线程。 3. setKeepAliveTime(long time, TimeUnit unit):设置线程池中空闲线程的存活时间。该方法可以动态修改线程池中空闲线程的存活时间。如果当前线程池中的线程数大于核心线程数,且空闲时间超过设定时间,则会销毁该线程。如果当前线程池中的线程数小于等于核心线程数,则不会销毁线程。 4. setRejectedExecutionHandler(RejectedExecutionHandler handler):设置线程池的拒绝策略。该方法可以动态修改线程池的拒绝策略,即当任务队列已满且线程池中的线程数已达到最大线程数时,如何处理新加入的任务。可以通过实现RejectedExecutionHandler接口自定义拒绝策略。 5. prestartCoreThread():预启动核心线程。该方法可以动态地预启动一个核心线程,即使当前线程池中没有任务需要执行。可以通过调用该方法提前创建线程,加快任务执行的响应速度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值