Java线程池如何合理配置核⼼线程数
我相信⼤家都⽤过线程池,但是线程池数量设置为多少⽐较合理呢?
线程数的设置的最主要的⽬的是为了充分并合理地使⽤ CPU 和内存等资源,从⽽最⼤限度地提⾼程序的性能,因此让我们⼀起去探索吧!⾸先要考虑到 CPU 核⼼数,那么在 Java 中如何获取核⼼线程数?
可以使⽤ Runtime().availableProcessor() ⽅法来获取(可能不准确,作为参考)
在确认了核⼼数后,再去判断是 CPU 密集型任务还是 IO 密集型任务:
java线程池创建的四种CPU 密集型任务:
⽐如像加解密,压缩、计算等⼀系列需要⼤量耗费 CPU 资源的任务,⼤部分场景下都是纯 CPU 计算。IO 密集型任务:⽐如像 MySQL 数据库、⽂件的读写、⽹络通信等任务,这类任务不会特别消耗 CPU 资源,但是 IO 操作⽐较耗时,会占⽤⽐较多时间。在知道如何判断任务的类别后,让我们分两个场景进⾏讨论:
CPU 密集型任务
对于 CPU 密集型计算,多线程本质上是提升多核 CPU 的利⽤率,所以对于⼀个 8 核的 CPU,每个核⼀个线程,理论上创建 8 个线程就可以了。
如果设置过多的线程数,实际上并不会起到很好的效果。此时假设我们设置的线程数量是 CPU 核⼼数的 2 倍,因为计算任务⾮常重,会占⽤⼤量的 CPU 资源,所以这时 CPU 的每个核⼼⼯作基本都是满负荷的,
⽽我们⼜设置了过多的线程,每个线程都想去利⽤ CPU 资源来执⾏⾃⼰的任务,这就会造成不必要的上下⽂切换,此时线程数的增多并没有让性能提升,反⽽由于线程数量过多会导致性能下降。
因此,对于 CPU 密集型的计算场景,理论上线程的数量 = CPU 核数就是最合适的,不过通常把线程的数量设置为CPU 核数 +1,会实现最优的利⽤率。
即使当密集型的线程由于偶尔的内存页失效或其他原因导致阻塞时,这个额外的线程也能确保 CPU 的时钟周期不会被浪费,从⽽保证 CPU 的利⽤率。
如下图就是在⼀个 8 核 CPU 的电脑上,通过修改线程数来测试对 CPU 密集型任务(素数计算)的性能影响。
可以看到线程数⼩于 8 时,性能是很差的,在线程数多于处理器核⼼数对性能的提升也很⼩,因此可
以验证公式还是具有⼀定适⽤性的。
除此之外,我们最好还要同时考虑在同⼀台机器上还有哪些其他会占⽤过多 CPU 资源的程序在运⾏,然后对资源使⽤做整体的平衡。
IO 密集型任务
对于 IO 密集型任务最⼤线程数⼀般会⼤于 CPU 核⼼数很多倍,因为 IO 读写速度相⽐于 CPU 的速度⽽⾔是⽐较慢的,如果我们设置过少的线程数,就可能导致 CPU 资源的浪费。⽽如果我们设置更多的线程数,那么当⼀部分线程正在等待 IO 的时候,它们此时并不需要 CPU 来计算,那么另外的线程便可以利⽤ CPU 去执⾏其他的任务,互不影响,这样的话在任务队列中等待的任务就会减少,可以更好地利⽤资源。
对于 IO 密集型计算场景,最佳的线程数是与程序中 CPU 计算和 IO 操作的耗时⽐相关的,《Java并发编程实战》的作者 Brain Goetz 推荐的计算⽅法如下:
线程数 = CPU 核⼼数 * (1 + IO 耗时/ CPU 耗时)
通过这个公式,我们可以计算出⼀个合理的线程数量,如果任务的平均等待时间长,线程数就随之增加,⽽如果平均⼯作时间长,也就是对于我们上⾯的 CPU 密集型任务,线程数就随之减少。
可以采⽤ APM ⼯具统计到每个⽅法的耗时,便于计算 IO 耗时和 CPU 耗时。
在这⾥引⽤Java并发编程实战中的图,⽅便⼤家更容易理解:
还有⼀派的计算⽅式是《Java虚拟机并发编程》中提出的:
线程数 = CPU 核⼼数 / (1 - 阻塞系数)
其中计算密集型阻塞系数为 0,IO 密集型阻塞系数接近 1,⼀般认为在 0.8 ~ 0.9 之间。⽐如 8 核 CPU,按照公式就是 2 / ( 1 - 0.9 ) = 20 个线程数
上图是 IO 密集型任务的⼀个测试,是在双核处理器上开不同的线程数(从 1 到 40)来测试对程序性能的影响,可以看到线程池数量达到20 之后,曲线逐渐⽔平,说明开再多的线程对程序的性能提升也毫⽆帮助。
太少的线程数会使得程序整体性能降低,⽽过多的线程也会消耗内存等其他资源,所以如果想要更准确的话,可以进⾏压测,监控 JVM 的线程情况以及 CPU 的负载情况,根据实际情况衡量应该创建的线程数,合理并充分利⽤资源。
同时,有很多线程池的应⽤,⽐如 Tomcat、Redis、Jdbc 等,每个应⽤设置的线程数也是不同的,⽐如 Tomcat 为流量⼊⼝,那么线程数的设置可能就要⽐其他应⽤要⼤。
总结
通过对线程数设置的探究,我们可以得知线程数的设置⾸先和 CPU 核⼼数有莫⼤关联,除此之外,我们需要根据任务类型的不同选择对应的策略,
线程的平均⼯作时间所占⽐例越⾼,就需要越少的线程;
线程的平均等待时间所占⽐例越⾼,就需要越多的线程;
针对不同的程序,进⾏对应的实际测试就可以得到最合适的选择。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。