Java併發編程 - 線程池

線程池是一種生產者/消費者模式的實現.

線程池處理任務的流程

ThreadPoolExecutor是一種線程池的實現, 它執行execute()的處理流程如下:

threadpoolexecutor-process

上圖中與新創建線程有關的步驟都需要獲取全局鎖, 所以線程池中應當儘量避免進行新線程的創建.
實際上在ThreadPoolExecutor完成預熱之後(corePoolSize已滿)的時候, 幾乎所有的execute()方法都是執行入隊操作.
這樣就避免了全局鎖的獲取(注意: 並不是說入隊不需要獲取鎖, 只是這時候獲取的不是全局鎖而已).

線程池的使用

線程池的創建

可以通過ThreadPoolExecutor來創建一個線程池:

new ThreadPoolExecutor(
    corePoolSize, // 線程池的基本大小, 小於此數值時僅新建線程
    maximumPoolSize, 
    keepAliveTime, // 線程池的工作線程空閒時, 保持存活的時間.
    milliseconds, 
    runnableTaskQueue, // 任務隊列, 需要使用阻塞隊列
    handler); // 當線程池和隊列都滿了的時候的丟棄策略

向線程池提交任務

兩種方法:

  1. 使用execute(), 適用於提交不需要返回值的任務, 所以也無法判斷任務是否被線程池執行成功;

    threadPool.execute(new Runnable() {
        @override
        public void run() {
            // 這裏是具體的代碼
        }
    });
    
  2. 使用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方法.
例如: 監控任務的平均執行時間, 最大執行時間, 最小執行時間等.

發佈了38 篇原創文章 · 獲贊 37 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章