来源:juejin.cn/post/6948034657321484318很多人可能看过一个设置线程数的理论:CPU密集型程序-核心数+1I/O密集型程序-核心数*2没办法,不会吧,真的有人按照这个理论来规划线程数?线程数和CPU利用率的小测试抛开一些操作系统和计算机原理,我们说一个基础理论(不严谨,只是为了方便理解):一个CPU核每次只能执行一个线程unittimeInstructions理论上,我只需要在一个线程中不停的执行指令,就可以跑到一个核的满利用率。下面写一个死循环空跑的例子来验证一下:测试环境:AMDRyzen53600,6-Core,12-ThreadspublicclassCPUUtilizationTest{publicstaticvoidmain(String[]args){//死循环,什么Don'tdowhile(true){}}}运行完这个例子,我们来看一下当前的CPU利用率:从图中可以看出,我的3号核的利用率已经被充分利用了。基于以上理论,我应该尝试多少个线程?公共类CPUUtilizationTest{publicstaticvoidmain(String[]args){for(intj=0;j<6;j++){newThread(newRunnable(){@Overridepublicvoidrun(){while(true){}}})。开始();}}}看看此时的CPU利用率,1/2/5/7/9/11几个核的利用率都跑满了:那么如果开12个线程,会不会跑满所有核的利用率呢?答案一定是肯定的:如果此时我继续将上面例子中的线程数增加到24个线程,会发生什么情况呢?从上图可以看出,CPU利用率和上一步一样,所有核心依然是100%,但是此时负载已经从11.x增加到22.x(对于loadaverage的解释,参考scoutapm.com/blog/unders…),说明此时CPU比较忙,线程的任务不能及时执行。现代CPU基本上是多核的。比如我这里测试的AMD3600就是6核12线程(超线程)。我们可以简单的认为它是一个12核的CPU。那么我的CPU可以同时做12件事情,互不干扰。如果要执行的线程数大于核心数,则需要操作系统进行调度。操作系统为每个线程分配CPU时间片资源,然后不断地切换它们,从而达到“并行”执行的效果。但它真的更快吗?从上面的例子可以看出,一个线程可以充分利用一个核的利用率。如果每个线程都非常“霸道”,不停地执行指令,不给CPU空闲时间,同时执行的线程数大于CPU的核心数,就会导致操作系统更频繁地执行切换线程以确保每个线程都能得到执行。但是切换是有代价的,每次切换都会伴随寄存器数据更新、内存页表更新等操作。虽然一次切换的开销与I/O操作相比微不足道,但是如果线程过多,线程切换过于频繁,甚至单位时间内的切换时间大于程序执行时间,就会导致CPU占用过多资源。花费精力在上下文切换而不是执行程序上是不值得的。上面这个死循环运行的例子有点过于极端了,一般情况下不太可能有这样的程序。大多数程序在运行时都会有一些I/O操作,可能是读写文件、通过网络发送和接收消息等,这些I/O操作在进行时需要等待反馈。比如在网络上读写时,需要等待消息的发送或接收。在这个等待过程中,线程处于等待状态,CPU不工作。这时操作系统会调度CPU去执行其他线程的指令,完美的利用了CPU的空闲时间,提高了CPU的利用率。上面的例子中,程序一直循环,什么都不做,CPU要一直执行指令,几乎没有空闲时间。如果插入一个I/O操作,在I/O操作过程中CPU处于空闲状态,CPU利用率会发生什么变化?先看单线程下的结果:publicclassCPUUtilizationTest{publicstaticvoidmain(String[]args)throwsInterruptedException{for(intn=0;n<1;n++){newThread(newRunnable(){@Overridepublicvoidrun(){while(true){//每次空循环1亿次后,sleep50ms,模拟I/O等待,switchfor(inti=0;i<100_000_000l;i++){}try{Thread.sleep(50);}catch(InterruptedExceptione){e.printStackTrace();}}}}).start();}}}哇塞,唯一9号核心的利用率才50%。比起之前100%不睡觉,降低了一半。现在把线程数调到12看:单核的利用率大概是60,和刚才的单线程结果相差不大,CPU利用率还没有完全跑出来。现在将线程数增加到18:单核此时利用率接近100%。可见,当线程中有I/O等不占用CPU资源的操作时,操作系统可以调度CPU同时执行更多的线程。现在提高I/O事件的频率,周期数减半,50_000_000,也是18个线程:此时每个核心的利用率只有70%左右。线程数与CPU利用率的小总结上面的例子只是一个辅助,为了更好的理解线程数/程序行为/CPU状态之间的关系,我们简单总结一下:一个极端的线程(不间断执行“计算”类操作时间),单核的利用率可以充分发挥,而多核CPU只能同时执行与核数相等的“极限”线程数。如果每个线程都这么“极端”,同时执行的线程数超过核心数,会导致不必要的切换,导致负载过大,只会让CPU在执行暂停操作时空闲,比如我/O,操作系统调度CPU去执行其他线程,可以提高CPU利用率,执行更多的多线程I/O事件频率越高,或者等待/暂停时间越长,空闲时间越长CPU和较低的利用率。操作系统可以调度CPU执行更多的线程。线程数规划的公式前面的铺垫是为了帮助理解,现在我们来看书上的定义。《Java 并发编程实战》介绍了一个计算线程数的公式:Ncpu=CPU核心数Ncpu=CPU核心数Ncpu=CPU核心数Ucpu=目标CPU利用率,0<=Ucpu<=1Ucpu=目标CPU利用率,0<=Ucpu<=1Ucpu=目标CPU利用率,0<=Ucpu<=1WC=等待时间与计算时间之比\frac{W}{C}=等待时间与计算时间之比CW=等待时间与计算时间之比时间如果想让程序运行到CPU的目标利用率,需要线程数的公式为:Nthreads=Ncpu?Ucpu?(1+WC)Nthreads=NcpuUcpu(1+\frac{W}{C})Nthreads=Ncpu?Ucpu?(1+CW)公式很清楚了,现在我们试试上面的例子:如果我期望目标利用率是90%(多核90),那么线程数需要的是:核心数12利用率0.9(1+50(休眠时间)/50(循环50_000_000耗时))≈22现在调整线程数为22,看结果:现在CPU利用率为80左右+,接近预期。由于线程数量过多,仍然存在一些上下文切换的开销,再加上缺乏严格的测试用例,所以实际使用率较低是正常的。将公式化成一个形状,通过线程数计算CPU利用率:Ucpu=NthreadsNcpu?(1+WC)Ucpu=\frac{Nthreads}{Ncpu*(1+\frac{W}{C})}Ucpu=Ncpu?(1+CW)Nthreads线程数22/(核心数12*(1+50(休眠时间)/50(周期耗时50_000_000)))≈0.9虽然公式很好,但是在实际程序中,一般很难得到准确的等待时间和计算时间,因为程序很复杂,不仅仅是“计算”。一段代码会有很多内存读写、计算、I/O等复杂操作,很难准确获取这两个指标,所以只通过公式计算线程数太理想了.实际程序中的线程数那么在实际程序中,或者在一些Java业务系统中,规划多少线程数(线程池大小)合适呢?先说结论:没有固定答案,先设定预期,比如我预期的CPU利用率是多少,负载是多少,GC频率是多少等指标,然后不断调整到一个合理的线程数通过测试,比如一个普通的,基于SpringBoot的业务系统,默认Tomcat容器+HikariCP连接池+G1收集器,如果此时项目也需要多线程(或线程池)用于业务场景异步/并行执行业务流程。这时候如果我按照上面的公式来规划线程数,误差肯定会很大。因为此时这台主机上已经有很多正在运行的线程,Tomcat有自己的线程池,HikariCP也有自己的后台线程,JVM也有一些编译线程,甚至G1也有自己的后台线程。这些线程同样运行在当前进程和当前主机上,同样会占用CPU资源。因此,在环境的干扰下,单靠公式很难准确规划出线程数,必须通过测试来验证。进程一般是这样的:分析当前主机,是否有其他进程干扰?分析当前JVM进程,有没有其他正在运行或者可能正在运行的线程?设置target目标CPU利用率——我能容忍我的CPU飙升到什么程度?TargetGCfrequency/pausetime-多线程执行后,GC频率会增加,可以容忍的最大频率是多少,每次暂停时间是多长?执行效率——比如在批处理中,我需要在单位时间内开启多少个线程才能及时完成处理...梳理一下链接的关键点,看看有没有瓶颈,因为如果线程过多,链路上部分节点资源有限,可能会导致大量线程等待资源(如三方接口限流,连接池数量受限,中间件压力大)toohightosupport等)不断增加/减少测试的线程数,按照最高的要求进行测试,最终得到一个“满足要求”的线程数**andandandand!不同场景下线程数的概念也不同:Tomcat中的maxThreads在BlockingI/O和No-BlockingI/O下是不一样的。Dubbo默认还是单连接,有I/O线程(pools)和业务线程(Pool)区分,I/O线程一般不是瓶颈,不需要太多,但是业务线程很容易被称为瓶颈。Redis6.0及以后也是多线程的,但只是I/O多线程,“业务”处理还是单线程的。线程所以,不用担心要设置多少个线程。没有标准答案,必须结合场景,结合目标,通过测试找到最合适的线程数。可能有同学会有疑问:“我们的系统没有任何压力,不需要这么合适的线程数,只是一个简单的异步场景,不影响系统的其他功能。”很正常,内部业务系统很多,而且不需要性能,稳定易用满足需求。那么我推荐的线程数是:NumberofCPUcoresAppendixJava获取CPU核心数Runtime.getRuntime().availableProcessors()//获取逻辑核心数,比如6核12线程,然后返回12Linux获取CPU核数#总核数=物理CPU数X每个物理CPU核数#逻辑CPU总数=物理CPU数X每个物理CPU核数X超线程数#查看数物理CPUcat/proc/cpuinfo|grep“物理ID”|排序|唯一|wc-l#查看每个物理CPU的核心数(也就是核心数)cat/proc/cpuinfo|grep“cpu核心”|uniq#查看逻辑CPU个数cat/proc/cpuinfo|grep“处理器”|wc-l感谢您的阅读,也欢迎您对本文提出任何建议,关注我,技术不输!小编在高速上与你相见。近期热点文章推荐:1.1,000+Java面试题及答案(2021最新版)2.别在满屏的if/else中,试试策略模式,真的很好吃!!3.操!Java中xx≠null的新语法是什么?4、SpringBoot2.5发布,深色模式太炸了!5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!
