Java线程池如何合理配置核心线程数
我相信大家都用過線程池,但是線程池數(shù)量設(shè)置為多少比較合理呢?
線程數(shù)的設(shè)置的最主要的目的是為了充分并合理地使用 CPU 和內(nèi)存等資源,從而最大限度地提高程序的性能,因此讓我們一起去探索吧!
首先要考慮到 CPU 核心數(shù),那么在 Java 中如何獲取核心線程數(shù)?
可以使用 Runtime.getRuntime().availableProcessor() 方法來獲取(可能不準(zhǔn)確,作為參考)
在確認(rèn)了核心數(shù)后,再去判斷是 CPU 密集型任務(wù)還是 IO 密集型任務(wù):
CPU 密集型任務(wù):
比如像加解密,壓縮、計算等一系列需要大量耗費 CPU 資源的任務(wù),大部分場景下都是純 CPU 計算。IO 密集型任務(wù):比如像 MySQL 數(shù)據(jù)庫、文件的讀寫、網(wǎng)絡(luò)通信等任務(wù),這類任務(wù)不會特別消耗 CPU 資源,但是 IO 操作比較耗時,會占用比較多時間。在知道如何判斷任務(wù)的類別后,讓我們分兩個場景進(jìn)行討論:
CPU 密集型任務(wù)
對于 CPU 密集型計算,多線程本質(zhì)上是提升多核 CPU 的利用率,所以對于一個 8 核的 CPU,每個核一個線程,理論上創(chuàng)建 8 個線程就可以了。
如果設(shè)置過多的線程數(shù),實際上并不會起到很好的效果。此時假設(shè)我們設(shè)置的線程數(shù)量是 CPU 核心數(shù)的 2 倍,因為計算任務(wù)非常重,會占用大量的 CPU 資源,所以這時 CPU 的每個核心工作基本都是滿負(fù)荷的,
而我們又設(shè)置了過多的線程,每個線程都想去利用 CPU 資源來執(zhí)行自己的任務(wù),這就會造成不必要的上下文切換,此時線程數(shù)的增多并沒有讓性能提升,反而由于線程數(shù)量過多會導(dǎo)致性能下降。
因此,對于 CPU 密集型的計算場景,理論上線程的數(shù)量 = CPU 核數(shù)就是最合適的,不過通常把線程的數(shù)量設(shè)置為CPU 核數(shù) +1,會實現(xiàn)最優(yōu)的利用率。
即使當(dāng)密集型的線程由于偶爾的內(nèi)存頁失效或其他原因?qū)е伦枞麜r,這個額外的線程也能確保 CPU 的時鐘周期不會被浪費,從而保證 CPU 的利用率。
如下圖就是在一個 8 核 CPU 的電腦上,通過修改線程數(shù)來測試對 CPU 密集型任務(wù)(素數(shù)計算)的性能影響。
可以看到線程數(shù)小于 8 時,性能是很差的,在線程數(shù)多于處理器核心數(shù)對性能的提升也很小,因此可以驗證公式還是具有一定適用性的。
除此之外,我們最好還要同時考慮在同一臺機(jī)器上還有哪些其他會占用過多 CPU 資源的程序在運行,然后對資源使用做整體的平衡。
IO 密集型任務(wù)
對于 IO 密集型任務(wù)最大線程數(shù)一般會大于 CPU 核心數(shù)很多倍,因為 IO 讀寫速度相比于 CPU 的速度而言是比較慢的,如果我們設(shè)置過少的線程數(shù),就可能導(dǎo)致 CPU 資源的浪費。而如果我們設(shè)置更多的線程數(shù),那么當(dāng)一部分線程正在等待 IO 的時候,它們此時并不需要 CPU 來計算,那么另外的線程便可以利用 CPU 去執(zhí)行其他的任務(wù),互不影響,這樣的話在任務(wù)隊列中等待的任務(wù)就會減少,可以更好地利用資源。
對于 IO 密集型計算場景,最佳的線程數(shù)是與程序中 CPU 計算和 IO 操作的耗時比相關(guān)的,《Java并發(fā)編程實戰(zhàn)》的作者 Brain Goetz 推薦的計算方法如下:
線程數(shù) = CPU 核心數(shù) * (1 + IO 耗時/ CPU 耗時)
通過這個公式,我們可以計算出一個合理的線程數(shù)量,如果任務(wù)的平均等待時間長,線程數(shù)就隨之增加,而如果平均工作時間長,也就是對于我們上面的 CPU 密集型任務(wù),線程數(shù)就隨之減少。
可以采用 APM 工具統(tǒng)計到每個方法的耗時,便于計算 IO 耗時和 CPU 耗時。
在這里引用Java并發(fā)編程實戰(zhàn)中的圖,方便大家更容易理解:
還有一派的計算方式是《Java虛擬機(jī)并發(fā)編程》中提出的:
線程數(shù) = CPU 核心數(shù) / (1 - 阻塞系數(shù))
其中計算密集型阻塞系數(shù)為 0,IO 密集型阻塞系數(shù)接近 1,一般認(rèn)為在 0.8 ~ 0.9 之間。比如 8 核 CPU,按照公式就是 2 / ( 1 - 0.9 ) = 20 個線程數(shù)
上圖是 IO 密集型任務(wù)的一個測試,是在雙核處理器上開不同的線程數(shù)(從 1 到 40)來測試對程序性能的影響,可以看到線程池數(shù)量達(dá)到 20 之后,曲線逐漸水平,說明開再多的線程對程序的性能提升也毫無幫助。
太少的線程數(shù)會使得程序整體性能降低,而過多的線程也會消耗內(nèi)存等其他資源,所以如果想要更準(zhǔn)確的話,可以進(jìn)行壓測,監(jiān)控 JVM 的線程情況以及 CPU 的負(fù)載情況,根據(jù)實際情況衡量應(yīng)該創(chuàng)建的線程數(shù),合理并充分利用資源。
同時,有很多線程池的應(yīng)用,比如 Tomcat、Redis、Jdbc 等,每個應(yīng)用設(shè)置的線程數(shù)也是不同的,比如 Tomcat 為流量入口,那么線程數(shù)的設(shè)置可能就要比其他應(yīng)用要大。
總結(jié)
通過對線程數(shù)設(shè)置的探究,我們可以得知線程數(shù)的設(shè)置首先和 CPU 核心數(shù)有莫大關(guān)聯(lián),除此之外,我們需要根據(jù)任務(wù)類型的不同選擇對應(yīng)的策略,
線程的平均工作時間所占比例越高,就需要越少的線程;
線程的平均等待時間所占比例越高,就需要越多的線程;
針對不同的程序,進(jìn)行對應(yīng)的實際測試就可以得到最合適的選擇。
總結(jié)
以上是生活随笔為你收集整理的Java线程池如何合理配置核心线程数的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 内存模型 C++ 和Java内存模型
- 下一篇: 面试复习计划
