如何设置线程数
# 如何设置线程数
大多数文章都在扯要区分是计算密集型还是IO密集型,但是实际情况远比区分这两种复杂的多,一个线程数的设置会收到多个方面的影响。
在这个方面,美团给出了一个回答,动态设置 《Java线程池实现原理及其在美团业务中的实践》 (opens new window)
线程池使用面临的核心的问题在于: 线程池的参数并不好配置 。
一方面线程池的运行机制不是很好理解,配置合理需要强依赖开发人员的个人经验和知识;
另一方面,线程池执行的情况和任务类型相关性较大,IO密集型和CPU密集型的任务运行起来的情况差异非常大,这导致业界并没有一些成熟的经验策略帮助开发人员参考。
美团给出的方案是线程池参数动态化:
尽管经过谨慎的评估,仍然不能够保证一次计算出来合适的参数,那么我们是否可以将修改线程池参数的成本降下来,这样至少可以发生故障的时候可以快速调整从而缩短故障恢复的时间呢?
基于这个思考,我们是否可以将线程池的参数从代码中迁移到分布式配置中心上,实现线程池参数可动态配置和即时生效
# 现有的解决方案
目前市场上都是在区分IO密集型
还是计算密集型
在《Java并发编程实战》是这么描述的
假设机器有N个CPU,那么对于计算密集型的任务,应该设置线程数为N+1;
对于IO密集型的任务,应该设置线程数为2N;
对于同时有计算工作和IO工作的任务,应该考虑使用两个线程池,一个处理计算任务,一个处理IO任务,分别对两个线程池按照计算密集型和IO密集型来设置线程数。
这种方式有几个问题:
- 实际项目中,往往会有多个线程池对业务进行一个线程隔离,如果都是2*N的线程数量,肯定是不合理的
- 线程池承担的流量不可能是均衡的,每个时间点都会由于自身的业务特点,出现毛刺
- 线程池尝尝会被用于调用下游接口,线程池设置了2*N,直接把下游打挂了
所以,种种问题催生出了美团的动态化配置,动态化配置+线程池监控完美的解决的以上的问题。
# 动态更新的原理
JDK原生线程池ThreadPoolExecutor提供了如下几个public的setter方法,如下图所示:
JDK允许线程池使用方通过ThreadPoolExecutor的实例来动态设置线程池的核心策略,以setCorePoolSize为方法例,在运行期线程池使用方调用此方法设置corePoolSize之后,线程池会直接覆盖原来的corePoolSize值,并且基于当前值和原始值的比较结果采取不同的处理策略。
对于当前值小于当前工作线程数的情况,说明有多余的worker线程,此时会向当前idle的worker线程发起中断请求以实现回收,多余的worker在下次idel的时候也会被回收;
对于当前值大于原始值且当前队列中有待执行任务,则线程池会创建新的worker线程来执行队列任务,setCorePoolSize具体流程如下:
# 面试考点
线程池被创建后里面有线程吗?如果没有的话,你知道有什么方法对线程池进行预热吗?
线程池被创建后如果没有任务过来,里面是不会有线程的。
可以通过
prestartAllCoreThreads
方法启动所有线程或者通过
prestartCoreThread
预先启动一个线程核心线程数会被回收吗?需要什么设置?
核心线程数默认是不会被回收的,如果需要回收核心线程数,需要调用
allowCoreThreadTimeOut
该值默认为false