線程池ThreadPoolExecutor——基礎分析!

1.線程池的作用

一方面當執行大量異步任務時候線程池能夠提供較好的性能,在不使用線程池的時候,每當需要執行異步任務時候是直接 new 一線程進行運行,而線程的創建和銷燬是需要開銷的。使用線程池時候,線程池裏面的線程是可複用的,不會每次執行異步任務時候都重新創建和銷燬線程。

另一方面線程池提供了一種資源限制和管理的手段,比如可以限制線程的個數,動態新增線程等,每個 ThreadPoolExecutor 也保留了一些基本的統計數據,比如當前線程池完成的任務數目等。

2.ThreadPoolExecutor 原理探究

類圖如下:

3.線程池狀態含義:

  • RUNNING:接受新任務並且處理阻塞隊列裏的任務;

  • SHUTDOWN:拒絕新任務但是處理阻塞隊列裏的任務;

  • STOP:拒絕新任務並且拋棄阻塞隊列裏的任務,同時會中斷正在處理的任務;

  • TIDYING:所有任務都執行完(包含阻塞隊列裏面任務)當前線程池活動線程爲 0,將要調用 terminated 方法;

  • TERMINATED:終止狀態,terminated方法調用完成以後的狀態。

 線程池狀態轉換:

       1.RUNNING -> SHUTDOWN:顯式調用 shutdown() 方法,或者隱式調用了 finalize(),它裏面調用了 shutdown() 方法。

       2.RUNNING or SHUTDOWN -> STOP:顯式調用 shutdownNow() 方法時候。

       3.SHUTDOWN -> TIDYING:當線程池和任務隊列都爲空的時候。

       4.STOP -> TIDYING:當線程池爲空的時候。

       5.TIDYING -> TERMINATED:當 terminated() hook 方法執行完成時候。

 

4.線程池參數:

  • corePoolSize:線程池核心線程個數;

  • workQueue:用於保存等待執行的任務的阻塞隊列;比如基於數組的有界 ArrayBlockingQueue,基於鏈表的無界 LinkedBlockingQueue,最多隻有一個元素的同步隊列 SynchronousQueue,優先級隊列 PriorityBlockingQueue 等。

  • maximunPoolSize:線程池最大線程數量。

  • ThreadFactory:創建線程的工廠。

  • RejectedExecutionHandler:飽和策略,當隊列滿了並且線程個數達到 maximunPoolSize 後採取的策略,比如 AbortPolicy (拋出異常),CallerRunsPolicy(使用調用者所在線程來運行任務),DiscardOldestPolicy(調用 poll 丟棄一個任務,執行當前任務),DiscardPolicy(默默丟棄,不拋出異常)。

  • keeyAliveTime:存活時間。如果當前線程池中的線程數量比核心線程數量要多,並且是閒置狀態的話,這些閒置的線程能存活的最大時間。

  • TimeUnit,存活時間的時間單位。

5.線程池類型:

1.newFixedThreadPool:創建一個核心線程個數和最大線程個數都爲 nThreads 的線程池,並且阻塞隊列長度爲Integer.MAX_VALUEkeeyAliveTime=0 說明只要線程個數比核心線程個數多並且當前空閒則回收。代碼如下:

  public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

  public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

2.newSingleThreadExecutor:創建一個核心線程個數和最大線程個數都爲1的線程池,並且阻塞隊列長爲 Integer.MAX_VALUEkeeyAliveTime=0 說明只要線程個數比核心線程個數多並且當前空閒則回收。代碼如下:

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

3.newCachedThreadPool:創建一個按需創建線程的線程池,初始線程個數爲 0,最多線程個數爲 Integer.MAX_VALUE,並且阻塞隊列爲同步隊列,keeyAliveTime=60 說明只要當前線程 60s 內空閒則回收。這個特殊在於加入到同步隊列的任務會被馬上被執行,同步隊列裏面最多隻有一個任務。代碼如下:

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

4.ScheduledExecutorService可以用來做定時任務的線程池

6.線程池拒絕策略

1.AbortPolicy,拒絕並拋出異常

public static class AbortPolicy implements RejectedExecutionHandler {
       
        public AbortPolicy() { }
 
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }

2.DiscardPolicy,拒絕但不拋出異常

 public static class DiscardPolicy implements RejectedExecutionHandler {

        public DiscardPolicy() { }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

3.DiscardOldestPolicy,執行任務,放棄最老的一個任務

public static class DiscardOldestPolicy implements RejectedExecutionHandler {

        public DiscardOldestPolicy() { }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }

4.CallerRunsPolicy 直接執行任務

public static class CallerRunsPolicy implements RejectedExecutionHandler {
        
        public CallerRunsPolicy() { }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

7合理配置線程池

 

CPU密集

CPU密集的意思是該任務需要大量的運算,而沒有阻塞,CPU一直全速運行。

CPU密集任務只有在真正的多核CPU上纔可能得到加速(通過多線程),而在單核CPU上,無論你開幾個模擬的多線程,該任務都不可能得到加速,因爲CPU總的運算能力就那些。

IO密集

IO密集型,即該任務需要大量的IO,即大量的阻塞。在單線程上運行IO密集型的任務會導致浪費大量的CPU運算能力浪費在等待。所以在IO密集型任務中使用多線程可以大大的加速程序運行,即時在單核CPU上,這種加速主要就是利用了被浪費掉的阻塞時間。

 

接着上一篇探討線程池留下的尾巴,如何合理的設置線程池大小。

要想合理的配置線程池的大小,首先得分析任務的特性,可以從以下幾個角度分析:

1.  任務的性質:CPU密集型任務、IO密集型任務、混合型任務。

2.  任務的優先級:高、中、低。

3.  任務的執行時間:長、中、短。

4.  任務的依賴性:是否依賴其他系統資源,如數據庫連接等。

性質不同的任務可以交給不同規模的線程池執行。

對於不同性質的任務來說,CPU密集型任務應配置儘可能小的線程,如配置CPU個數+1的線程數,IO密集型任務應配置儘可能多的線程,因爲IO操作不佔用CPU,不要讓CPU閒下來,應加大線程數量,如配置兩倍CPU個數+1,而對於混合型的任務,如果可以拆分,拆分成IO密集型和CPU密集型分別處理,前提是兩者運行的時間是差不多的,如果處理時間相差很大,則沒必要拆分了。

若任務對其他系統資源有依賴,如某個任務依賴數據庫的連接返回的結果,這時候等待的時間越長,則CPU空閒的時間越長,那麼線程數量應設置得越大,才能更好的利用CPU。

當然具體合理線程池值大小,需要結合系統實際情況,在大量的嘗試下比較才能得出,以上只是前人總結的規律。

 

最佳線程數目 = ((線程等待時間+線程CPU時間)/線程CPU時間 )* CPU數目

比如平均每個線程CPU運行時間爲0.5s,而線程等待時間(非CPU運行時間,比如IO)爲1.5s,CPU核心數爲8,那麼根據上面這個公式估算得到:((0.5+1.5)/0.5)*8=32。這個公式進一步轉化爲:

最佳線程數目 = (線程等待時間與線程CPU時間之比 + 1)* CPU數目

可以得出一個結論: 
線程等待時間所佔比例越高,需要越多線程。線程CPU時間所佔比例越高,需要越少線程。 
以上公式與之前的CPU和IO密集型任務設置線程數基本吻合。

CPU密集型時,任務可以少配置線程數,大概和機器的cpu核數相當,這樣可以使得每個線程都在執行任務

IO密集型時,大部分線程都阻塞,故需要多配置線程數,2*cpu核數

操作系統之名稱解釋:

某些進程花費了絕大多數時間在計算上,而其他則在等待I/O上花費了大多是時間,

前者稱爲計算密集型(CPU密集型)computer-bound,後者稱爲I/O密集型,I/O-bound。

歡迎觀看我下一篇,線程池ThreadPoolExecutor源碼深度解析!https://blog.csdn.net/zhangkaixuan456/article/details/106841419

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