Java執行緒池如何合理配置核心執行緒數
我相信大家都用過執行緒池,但是執行緒池數量設定為多少比較合理呢?
執行緒數的設定的最主要的目的是為了充分併合理地使用 CPU 和記憶體等資源,從而最大限度地提高程式的效能,因此讓我們一起去探索吧!
首先要考慮到 CPU 核心數,那麼在 Java 中如何獲取核心執行緒數?
可以使用 Runtime.getRuntime().availableProcessor() 方法來獲取(可能不準確,作為參考)
在確認了核心數後,再去判斷是 CPU 密集型任務還是 IO 密集型任務:
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 核心數有莫大關聯,除此之外,我們需要根據任務型別的不同選擇對應的策略,
執行緒的平均工作時間所佔比例越高,就需要越少的執行緒;
執行緒的平均等待時間所佔比例越高,就需要越多的執行緒;
針對不同的程式,進行對應的實際測試就可以得到最合適的選擇。
參考:
https://www.cnblogs.com/651434092qq/p/14240406.html