歡迎光臨
每天分享高質量文章

Java執行緒池容量設定

建立執行緒池的方式

Java中可以透過Executors和ThreadPoolExecutor的方式建立執行緒池,透過Executors可以快速建立四種常見的執行緒池,但這種方式在實際使用中並不推薦,因為這種方式創建出來的執行緒池可控性較差,更推薦的方式是使用ThreadPoolExecutor提供的方法。參考阿裡巴巴Java開發規範:

【強制】執行緒池不允許使用Executors去建立,而是透過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學更明確執行緒池的執行規則,規避資源耗盡的風險。 說明:Executors傳回的執行緒池物件弊端如下: 1)FixedThreadPool和SingleThreadPool:允許的請求佇列長度為Integer.MAXVALUE,可能會堆積大量的請求,從而導致OOM。 2)CacheThreadPool和ScheduledThreadPool:允許建立執行緒數量為Integer.MAXVALUE,可能會建立大量執行緒,從而導致OOM。

引數的工作原理

使用ThreadPoolExecutor建立執行緒池需要自己給出關鍵的引數: + corePoolSize: + maximumPoolSize + keepAliveTime + unit + workQueue + threadFactory + handler

這幾大引數的工作原理如下: 

具體講解可以參考清英在infoQ釋出的文章:http://www.infoq.com/cn/articles/java-threadPool

執行緒池容量設定

執行緒池的corePoolSize設定是整個執行緒池中最關鍵的引數,設定太小會導致執行緒池的吞吐量不足,因為新提交的任務需要排隊或者被handler處理掉(取決於拒絕策略);設定太大可能會耗盡計算機的CPU和記憶體資源。

CPU bound

在CS中,CPU密集型任務的執行時間主要取決於CPU的時間。CPU密集型任務的執行緒數通常設定為CPU核心數+1 ,《Java Concurrency in Practice》(由Doug. Lea等人撰寫)給出瞭如下的解釋:

Even computeͲintensive threads occasionally take a page fault or pause for some other reason, so an “extra” runnable thread prevents CPU cycles from going unused when this happens.

即比CPU核心數多出來的一個執行緒是為了防止執行緒偶發的page fault或者由於其他原因導致的任務暫停,此時CPU就會處於空閑狀態,而在這種情況下多出來的一個執行緒就可以充分利用CPU的這個空閑時間。

IO bound

IO密集型任務是指任務的執行時間主要取決於IO的時間。在筆者實習期間遇到的業務場景中大部分屬於IO bound。而對於IO bound,書中則給出瞭如下的計算公式:

最佳執行緒數 = CPU數量 * CPU利用率 *(執行緒等待時間/執行緒CPU時間 + 1)

實際最優引數

在實際操作中,由於公式中執行緒等待時間和執行緒CPU時間不好估算,以及系統中存在其他的阻塞情況,這樣計算出來的最佳執行緒數往往不是生產環境下的最佳執行緒數。為了提高精確度,筆者利用有贊內部的壓測系統對程式碼進行壓測,透過多次調整計算出來的最佳執行緒數和觀察壓測結果(系統負載、介面RT、TPS等指標)來判斷執行緒數是否符合期望。 在壓測的過程中發現,當執行緒數量設定的更合理時TPS更高且介面的RT較低;而執行緒池設定過大導致TPS下降和RT上漲。由於RT和TPS不太方便直接給出,這裡僅展示系統負載這一指標的壓測結果。 當執行緒池設定過大時: 當執行緒池設定較為合理時:

參考資料: 1. http://www.infoq.com/cn/articles/java-threadPool 2. https://en.wikipedia.org/wiki/I/O_bound 3. https://en.wikipedia.org/wiki/CPU-bound 4. 《Java Concurrency in Practice》

贊(0)

分享創造快樂