線程池是一種生產者/消費者模式的實現.
線程池處理任務的流程
ThreadPoolExecutor
是一種線程池的實現, 它執行execute()
的處理流程如下:
上圖中與新創建線程有關的步驟都需要獲取全局鎖, 所以線程池中應當儘量避免進行新線程的創建.
實際上在ThreadPoolExecutor
完成預熱之後(corePoolSize已滿)的時候, 幾乎所有的execute()
方法都是執行入隊操作.
這樣就避免了全局鎖的獲取(注意: 並不是說入隊不需要獲取鎖, 只是這時候獲取的不是全局鎖而已).
線程池的使用
線程池的創建
可以通過ThreadPoolExecutor
來創建一個線程池:
new ThreadPoolExecutor(
corePoolSize, // 線程池的基本大小, 小於此數值時僅新建線程
maximumPoolSize,
keepAliveTime, // 線程池的工作線程空閒時, 保持存活的時間.
milliseconds,
runnableTaskQueue, // 任務隊列, 需要使用阻塞隊列
handler); // 當線程池和隊列都滿了的時候的丟棄策略
向線程池提交任務
兩種方法:
-
使用
execute()
, 適用於提交不需要返回值的任務, 所以也無法判斷任務是否被線程池執行成功;threadPool.execute(new Runnable() { @override public void run() { // 這裏是具體的代碼 } });
-
使用
submit()
, 適用於提交需要返回值的任務, 會返回一個Future
類型的對象, 用於判斷任務是否執行成功.
可以通過Future
對象的get()
方法來獲取返回值.get()
會阻塞當前線程直到任務完成.Future<Object> future = threadPool.execute(hasReturnValueTask); try { Object o = future.get(); } catch(Exception e) { } finally { threadPool.shutdown(); }
關閉線程池
通常調用shutdown()
方法來關閉線程池, 確保所有正在執行的任務都正常完成;
如果當前任務不一定要執行完, 也可以使用shutdownNow()
來進行.
合理的配置線程池
以下以CPU的總核心數爲n來計算
核心線程數
需要根據任務的性質來具體劃分
- 如果是計算密集型任務(也就是CPU密集型任務, 大量快速執行的小任務), 則配置n+1個線程, 充分利用CPU的計算能力.
- 如果是IO密集型任務, 則單任務的等待時間長, 則應該配置儘可能多的線程, 比如2*n
- 如果是混合型的任務, 則可以配置兩個不同規模的線程池, 也可以使用優先級隊列, 讓執行時間短的任務先執行;
隊列的選擇
可以使用優先級隊列PriorityBlockingQueue
來處理, 讓優先級高的任務先執行.
另外, 建議使用有界隊列, 因爲在遇到IO問題時, 有界隊列只會發出拋棄任務的異常,
但如果使用了無界隊列, 則有可能不斷創建線程, 最終導致內存溢出, 影響其他線程.
線程池的監控
監控線程池的運作方便在出現問題時, 可以根據線程池的使用狀況快速定位問題, 也有利於進行調優.
在監控線程池的時候有如下屬性:
taskCount
: 需要執行的任務量;completedTaskCount
: 已完成的任務量, 小於等於taskCount
;largestPoolSize
: 池中的歷史最大線程數量, 可以據此判斷線程池是否滿過, 也就是是否用過隊列;getPoolSize
: 得到線程池的當前線程數量, 只增不減的一個值.getActiveCount
: 獲取活動的線程數;
要使用以上屬性, 需要創建線程池實現類的子類, 並重寫beforeExecute, afterExecute, terminated
方法.
例如: 監控任務的平均執行時間, 最大執行時間, 最小執行時間等.