大家好,又见面了,我是你们的朋友全栈君。
前言
着计算机行业的飞速发展,摩尔定律逐渐失效,多核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线等。
负载告警:线程池队列任务积压到一定值的时候会通过通讯工具,告知应用开发负责人;当线程池负载数达到一定阈值的时候会通过通讯工具告知应用开发负责人。
操作监控:创建/修改和删除线程池都会通知到应用的开发负责人。
操作日志:可以查看线程池参数的修改记录,谁在什么时候修改了线程池参数、修改前的参数值是什么。
权限校验:只有应用开发负责人才能够修改应用的线程池参数。
架构图:
参考自美团技术团队。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/138660.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...