你真的瞭解java線程池的使用嗎

線程池的使用

1、線程池的使用場景

  • 等待返回任務的結果的多步驟的處理場景, 批量併發執行任務,總耗時是單個步驟耗時最長的那個,提供整體的執行效率,

  • 最終一致性,異步執行任務,無需等待,快速返回

2、線程池的關鍵參數說明

一般情況下我們是通過ThreadPoolExecutor來構造我們的線程池對象的。

* 阿里巴巴的開發規範文檔是禁止直接使用Executors靜態工廠類來創建線程池的,原因是

【強制】線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這樣
的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。
說明: Executors 返回的線程池對象的弊端如下:
(1) FixedThreadPool 和 SingleThreadPool :
允許的請求隊列長度爲 Integer.MAX_VALUE ,可能會堆積大量的請求,從而導致 OOM 。
(2) CachedThreadPool 和 ScheduledThreadPool :
允許的創建線程數量爲 Integer.MAX_VALUE ,可能會創建大量的線程,從而導致 OOM 。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
       
  }

參數說明:

  • corePoolSize:核心線程數,線程池最低的線程數
  • maximumPoolSize:允許的最大的線程數
  • keepAliveTime:當前線程數超過corePoolSize的時候,空閒線程保留的時間
  • unit: keepAliveTime線程保留的時間的單位
  • workQueue: 任務緩衝區
  • threadFactory: 線程的構造工廠
  • handler: 線程池飽含時候的處理策略

3、線程池的分類

Java通過Executors提供四種線程池,分別爲:

  • newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。
  • newFixedThreadPool 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。
  • newScheduledThreadPool 創建一個定長線程池,支持定時及週期性任務執行。
  • newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。
3.1、newCachedThreadPool
public static ExecutorService newCachedThreadPool(){
    return new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.MILLISECONDS,new SynchronousQueue<Runnable>());
}

它是一個可以無限擴大的線程池;

  • 它比較適合處理執行時間比較小的任務;

  • corePoolSize爲0,maximumPoolSize爲無限大,意味着線程數量可以無限大;

  • keepAliveTime爲60S,意味着線程空閒時間超過60S就會被殺死;

  • 採用SynchronousQueue裝等待的任務,這個阻塞隊列沒有存儲空間,這意味着只要有請求到來,就必須要找到一條工作線程處理他,如果當前沒有空閒的線程,那麼就會再創建一條新的線程。

3.2、newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads){
    return new ThreadPoolExecutor(nThreads,nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
  • 它是一種固定大小的線程池;corePoolSize和maximunPoolSize都爲用戶設定的線程數量nThreads;
  • keepAliveTime爲0,意味着一旦有多餘的空閒線程,就會被立即停止掉;但這裏keepAliveTime無效;
  • 阻塞隊列採用了LinkedBlockingQueue,它是一個無界隊列;由於阻塞隊列是一個無界隊列,因此永遠不可能拒絕任務;
  • 由於採用了無界隊列,實際線程數量將永遠維持在nThreads,因此maximumPoolSize和keepAliveTime將無效。
3.3、ScheduledThreadPool
public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
  • 定時任務的使用
3.4、SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor(){
    return new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
  • 它只會創建一條工作線程處理任務;
  • 採用的阻塞隊列爲LinkedBlockingQueue;
3.5、總結
線程池 特點 建議使用場景
newCachedThreadPool 1、線程數無上限
2、空閒線程存活60s
3、阻塞隊列
1、任務執行時間短
2、任務要求響應時間短
newFixedThreadPool 1、線程數固定
2、無界隊列
1、任務比較平緩
2、控制最大的線程數
newScheduledThreadPool 核心線程數量固定、非核心線程數量無限制(閒置時馬上回收) 執行定時 / 週期性 任務
newSingleThreadExecutor 只有一個核心線程(保證所有任務按照指定順序在一個線程中執行,不需要處理線程同步的問題) 不適合併發但可能引起IO阻塞性及影響UI線程響應的操作,如數據庫操作,文件操作等

4、使用線程池容易出現的問題

現象 原因
整個系統影響緩慢,大部分504 1、爲設置最大的線程數,任務積壓過多,線程數用盡
oom 1、隊列無界或者size設置過大
使用線程池對效率並沒有明顯的提升 1、線程池的參數設置過小,線程數過小或者隊列過小,或者是服務器的cpu核數太低

5、線程池的監控

5.1、爲什麼要對線程池進行監控

  • 線程池中線程數和隊列的類型及長度對線程會造成很大的影響,而且會爭奪系統稀有資源,線程數。設置不當,或是沒有最大的利用系統資源,提高系統的整體運行效率,或是導致整個系統的故障。典型的場景是線程數被佔滿,其他的請求無響應。或是任務積壓過多,直接oom
  • 方便的排查線程中的故障以及優化線程池的使用

5.2、監控的原理

  • 另起一個定時單線程數的線程池newSingleThreadScheduledExecutor

  • 調用scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)定時執行監控任務;

  • 定時任務內 通過ThreadPoolExecutor對象獲取監控的對象信息,比如t線程池需要執行的任務數、線程池在運行過程中已完成的任務數、曾經創建過的最大線程數、線程池裏的線程數量、線程池裏活躍的線程數量、當前排隊線程數

  • 根據預設的日誌或報警策略,進行規則控制

5.3、實現的細節

定義線程池並啓動監控

 /**
     * 定義線程池的隊列的長度
     */
    private final Integer queueSize = 1000;

    /**
     * 定義一個定長的線程池
     */
    private ExecutorService executorService;

    @PostConstruct
    private void initExecutorService() {
        log.info(
                "executorService init with param: threadcount:{} ,queuesize:{}",
                systemConfig.getThreadCount(),
                systemConfig.getThreadQueueSize());
        executorService =
                new ThreadPoolExecutor(
                        systemConfig.getThreadCount(),
                        systemConfig.getThreadCount(),
                        0,
                        TimeUnit.MILLISECONDS,
                        new ArrayBlockingQueue(systemConfig.getThreadQueueSize()),
                        new BasicThreadFactory.Builder()
                                .namingPattern("async-sign-thread-%d")
                                .build(),
                        (r, executor) -> log.error("the async executor pool is full!!"));

        /** 啓動線程池的監控 */
        ThreadPoolMonitoring threadPoolMonitoring = new ThreadPoolMonitoring();
        threadPoolMonitoring.init();
    }

線程池的監控

/**
     * 功能說明:線程池監控
     *
     * @params
     * @return <br>
     *     修改歷史<br>
     *     [2019年06月14日 10:20:10 10:20] 創建方法by fengqingyang
     */
    public class ThreadPoolMonitoring {
        /** 用於週期性監控線程池的運行狀態 */
        private final ScheduledExecutorService scheduledExecutorService =
                Executors.newSingleThreadScheduledExecutor(
                        new BasicThreadFactory.Builder()
                                .namingPattern("async thread executor monitor")
                                .build());

        /**
         * 功能說明:自動運行監控
         *
         * @return <br>
         *     修改歷史<br>
         *     [2019年06月14日 10:26:51 10:26] 創建方法by fengqingyang
         * @params
         */
        public void init() {
            scheduledExecutorService.scheduleAtFixedRate(
                    () -> {
                        try {
                            ThreadPoolExecutor threadPoolExecutor =
                                    (ThreadPoolExecutor) executorService;
                            /** 線程池需要執行的任務數 */
                            long taskCount = threadPoolExecutor.getTaskCount();
                            /** 線程池在運行過程中已完成的任務數 */
                            long completedTaskCount = threadPoolExecutor.getCompletedTaskCount();
                            /** 曾經創建過的最大線程數 */
                            long largestPoolSize = threadPoolExecutor.getLargestPoolSize();
                            /** 線程池裏的線程數量 */
                            long poolSize = threadPoolExecutor.getPoolSize();
                            /** 線程池裏活躍的線程數量 */
                            long activeCount = threadPoolExecutor.getActiveCount();
                            /** 當前排隊線程數 */
                            int queueSize = threadPoolExecutor.getQueue().size();
                            log.info(
                                    "async-executor monitor. taskCount:{}, completedTaskCount:{}, largestPoolSize:{}, poolSize:{}, activeCount:{},queueSize:{}",
                                    taskCount,
                                    completedTaskCount,
                                    largestPoolSize,
                                    poolSize,
                                    activeCount,
                                    queueSize);

                            /** 超過閥值的80%報警 */
                            if (activeCount >= systemConfig.getThreadCount() * 0.8) {
                                log.error(
                                        "async-executor monitor. taskCount:{}, completedTaskCount:{}, largestPoolSize:{}, poolSize:{}, activeCount:{},queueSize:{}",
                                        taskCount,
                                        completedTaskCount,
                                        largestPoolSize,
                                        poolSize,
                                        activeCount,
                                        queueSize);
                                ;
                            }
                        } catch (Exception ex) {
                            log.error("ThreadPoolMonitoring service error,{}", ex.getMessage());
                        }
                    },
                    0,
                    30,
                    TimeUnit.SECONDS);
        }
    }

6、需要注意的事項

  • 線程數要合理設置,一般建議值是核數的2倍。
  • 線程池隊列的類型和長度要根據業特性合理設置
  • 不同的業務需要線程池隔離,避免相互影響
  • 未每個線程池增加特有的命名規範以及關鍵的日誌,方便出問題排查和優化

7、後續

更多精彩,敬請關注, 程序員導航網 https://chenzhuofan.top

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章