每日一博 - Review线程池_02
Posted 小小工匠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了每日一博 - Review线程池_02相关的知识,希望对你有一定的参考价值。
Pre
使用场景
为了最大程度利用CPU的多核性能,并行运算的能力是不可或缺的。通过线程池管理线程获取并发性是一个非常基础的操作,让我们来看两个典型的使用线程池获取并发性的场景。
场景1:响应速度优先
用户发起的实时请求,服务追求响应时间。
使用线程池也是有考量的,这种场景最重要的就是获取最大的响应速度去满足用户,所以应该不设置队列去缓冲并发任务,调高corePoolSize和maxPoolSize去尽可能创造多的线程快速执行任务。
场景2:吞吐量优先
和上一个响应速度优先的场景区别在于, 这类场景任务量巨大,并不需要瞬时的完成,而是关注如何使用有限的资源,尽可能在单位时间内处理更多的任务,也就是吞吐量优先的问题。
所以应该设置队列去缓冲并发任务,调整合适的corePoolSize去设置处理任务的线程数。在这里,设置的线程数过多可能还会引发线程上下文切换频繁的问题,也会降低处理任务的速度,降低吞吐量。
线程池设置不合理发生的那些故障
【CASE 1】: 接口大量调用降级 -----------> 使用线程池做并行计算,由于没有预估好调用的流量,导致最大核心数设置偏小,大量抛出RejectedExecutionException,触发接口降级条件
【CASE 2】: 服务执行时间过长,作为上游服务整体超时,大量下游服务调用失败 ------------> 内部逻辑使用线程池做资源隔离,由于队列设置过长,最大线程数设置失效,导致请求数量增加时,大量任务堆积在队列中,任务执行时间过长,最终导致下游服务的大量调用超时失败
总结下:
- 核心线程过小,阻塞队列过小,最大线程过小,导致接口频繁抛出拒绝策略异常
- 核心线程过小,阻塞队列过小,最大线程过大,导致线程调度开销增大,处理速度下降
- 核心线程过小,阻塞队列过大,导致任务堆积,接口响应或者程序执行时间拉长
- 核心线程过大,导致线程池内空闲线程过多,过多的占用系统资源,造成资源浪费
如果线程池的配置涉及到上述问题,那么就有可能需要修改代码,重启业务来解决;如果发布后参数仍不合理,继续…
线程池的参数如何评估和配置???
不用线程池?
业务使用线程池是为了获取并发性,对于获取并发性,是否可以有什么其他的方案呢替代
万能公式?
业界的一些线程池参数配置方案:
设置多少个线程数量通常是根据应用的类型:IO密集型、CPU密集型。
- IO密集型通常设置为2n+1,其中n为CPU核数
- CPU密集型通常设置为 n+1。
实际情况往往复杂得多
其实对于IO密集型类型的应用,网上还有一个公式:线程数 = CPU核心数/(1-阻塞系数)
引入了阻塞系数的概念,一般为0.8~0.9之间,
在我们的业务开发中,基本上都是IO密集型,因为往往都会去操作数据库,访问redis,es等存储型组件,都会涉及到磁盘IO,网络IO。
IO密集型,可以考虑多设置一些线程,主要目的是可以增加IO的并发度,CPU密集型不宜设置过多线程,因为是会造成线程切换,反而损耗性能。
并没有得出通用的线程池计算方式。并发任务的执行情况和任务类型相关,IO密集型和CPU密集型的任务运行起来的情况差异非常大,但这种占比是较难合理预估的,这导致很难有一个简单有效的通用公式能直接计算出结果。
线程池参数动态化?
bingo ,这是正确的方式
动态化线程池的核心设计包括以下三个方面:
-
简化线程池配置:线程池构造参数有8个,但是最核心的是3个:corePoolSize、maximumPoolSize,workQueue,它们最大程度地决定了线程池的任务分配和线程分配策略。
考虑到在实际应用中获取并发性的场景主要是两种:
(1)并行执行子任务,提高响应速度。这种情况下,应该使用同步队列,没有什么任务应该被缓存下来,而是应该立即执行。
(2)并行执行大批次任务,提升吞吐量。这种情况下,应该使用有界队列,使用队列去缓冲大批量的任务,队列容量必须声明,防止任务无限制堆积。
所以线程池只需要提供这三个关键参数的配置,并且提供两种队列的选择,就可以满足绝大多数的业务需求 -
参数可动态修改:为了解决参数不好配,修改参数成本高等问题。在Java线程池留有高扩展性的基础上,封装线程池,允许线程池监听同步外部的消息,根据消息进行修改配置。将线程池的配置放置在平台侧,允许查看、修改线程池配置。
-
增加线程池监控:对某事物缺乏状态的观测,就对其改进无从下手。在线程池执行任务的生命周期添加监控能力,帮助了解线程池状态。
1.现有的解决方案的痛点
2.动态更新的工作原理是什么
在运行期线程池使用方调用此方法设置corePoolSize之后,线程池会直接覆盖原来的corePoolSize值,并且基于当前值和原始值的比较结果采取不同的处理策略。
对于当前值小于当前工作线程数的情况,说明有多余的worker线程,此时会向当前idle的worker线程发起中断请求以实现回收,多余的worker在下次idel的时候也会被回收;
对于当前值大于原始值且当前队列中有待执行任务,则线程池会创建新的worker线程来执行队列任务,setCorePoolSize具体流程如下:
3.动态设置的注意点有哪些?
在设置核心线程的时候,同时设置最大线程数就可以。只要工作线程不大于最大线程数,那么动态设置就是有效的
4.如何动态指定队列长度
这一种方式简单粗暴,直接把 LinkedBlockingQueue 代码复制出来一份,改个新名字
ResizableCapacityLinkedBlockIngQueue
,然后把 capacity 所修饰的 final 关键字去掉,再加上一个 #setCapacity
方法
https://www.cnblogs.com/thisiswhy/p/12690630.html
https://juejin.cn/post/6991634257147854856
https://github.com/longtai94/dynamic-threadpool
线程池的监控
如果可以知道一部分线程池运行时指标,可以极大程度上的预防部分故障
- 监控业务线程池的 当前负载以及峰值负载
- 监控线程池在不同时间段 核心线程、最大线程、活跃线程数量指标
- 监控线程 池阻塞队列相关指标,判断是否有任务积压的风险
- 监控线程任务在 运行时抛出的异常数量,诊断投递的任务是否“健康”
- 监控线程池执行 拒绝策略执行的次数,确定线程池参数是否合理
以上是关于每日一博 - Review线程池_02的主要内容,如果未能解决你的问题,请参考以下文章
Java Review - 线程池中使用ThreadLocal不当导致的内存泄漏案例&源码分析
Java Review - 线程池中使用ThreadLocal不当导致的内存泄漏案例&源码分析
Java Review - 并发编程_ CountDownLatch原理&源码剖析
Java Review - 并发编程_ CountDownLatch原理&源码剖析