線程池的幾個靈魂拷問(二)

線程池雖然在并發編程里很強大,但線程池使用面臨的核心的問題在于:線程池的參數并不好配置。一方面線程池的運行機制不是很好理解,配置合理需要強依賴開發人員的個人經驗和知識;另一方面,線程池執行的情況和任務類型相關性較大,IO密集型和CPU密集型的任務運行起來的情況差異非常大,這導致業界并沒有一些成熟的經驗策略幫助開發人員參考。

美團方案

比如網上流傳的比較多的一個策略:

  • 如果是CPU密集型任務,就需要盡量壓榨CPU,參考值可以設為 N(CPU)+1(比如是4核心 就配置為5)
  • 如果是IO密集型任務,參考值可以設置為2*N(CPU)

CPU密集型的為什么要+1呢?《Java并發編程實戰》給出的原因是:即使當計算(CPU)密集型的線程偶爾由于頁缺失故障或者其他原因而暫停時,這個“額外”的線程也能確保 CPU 的時鐘周期不會被浪費。

這里先來看看美團幫我們總結的現在業界的一些線程池調參方案:

cpu參數方案

第一套方案是并發編程實戰給出的,明顯太理論化了,和實際業務想去甚遠!

N(threads) = N(Cpu個數)*U(cpu的使用率)*(1+ 等待時間/計算時間)

第二套方案就沒有考慮多個業務線程池的情況。
第三套方案的用到了TPS來參與計算,但是這也是流量恒定情況下算出來的,真實情況往往比較隨機。

有啥比較好的辦法嗎?——那就是:線程池參數動態化,采用這種方案最好就是用這么一個辦法來做:

  • 簡化線程池配置:線程池構造參數有8個,但是最核心的是3個:corePoolSize、maximumPoolSize,workQueue,它們最大程度地決定了線程池的任務分配和線程分配策略
  • 參數可動態修改:為了解決參數不好配,修改參數成本高等問題
  • 加線程池監控

為什么能做到動態修改線程池參數呢?這是因為JDK本身就提供api方法支持動態的修改:

設置核心線程數的大小

至于如何在運行時狀態實時查看,這里也有一個辦法:用戶基于JDK原生線程池ThreadPoolExecutor提供的幾個public的getter方法,可以讀取到當前線程池的運行狀態以及參數:

線程池的運行時狀態

用戶基于這個功能可以了解線程池的實時狀態,比如當前有多少個工作線程,執行了多少個任務,隊列中等待的任務數等等。

Netty進階指南給出來的方案

在Netty服務編寫的過程中,也要涉及到兩個線程池的參數配置,尤其是IO線程池的配置,這里書中也給了一套經驗方案來針對線程的監控情況,可以參考:
同樣的先用CPU核數*2,看看是否存在瓶頸,運行時的監控則用比較土的辦法了:

  • 打印thread dump,同時獲取當時cpu排在前面幾個的線程號
  • 然后在線程dump文件中去對應的線程號堆棧
  • 然后在堆棧中查找是否有SelectotImpl.lookAndDoSelect處的lock信息

如果多次采集都發現有這堆信息的話,說明此時此刻的IO線程比較空閑,無需調整;但是如果一直在read或者write的執行處,則說明IO較為繁忙,可以適當的去調大NioEventLoop線程的個數來提升網絡的讀寫性能。但是這邊線程數的改動就不是動態化的了,服務啟動后指定的線程數就不能再修改了。

參考文章


1、Java線程池實現原理及其在美團業務中的實踐
2、微信文章:如何設置線程池參數?美團給出了一個讓面試官虎軀一震的回答。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。