Java多執行緒-執行緒池的使用

執行緒池的優點

  • 執行緒頻繁的創建=>銷毀=>創建對系統對開銷很大,使用執行緒池可以避免重複的開銷
  • 方便復用,提高相應速度
  • 執行緒的創建於執行完全分開,方便維護,降低耦合

執行緒池的實現原理

池化技術

一說到執行緒池自然就會想到池化技術

其實所謂池化技術,就是把一些能夠復用的東西放到池中,避免重複創建、銷毀的開銷,從而極大提高性能。

常見池化技術的例如:

  • 執行緒池
  • 記憶體池
  • 連接池

Java中的實現

官方介面

JDK 1.5 推出了三大API用來創建執行緒:

  • Executors.newCachedThreadPool():無限執行緒池(最大21億)
  • Executors.newFixedThreadPool(nThreads):固定大小的執行緒池
  • Executors.newSingleThreadExecutor():單個執行緒的執行緒池

這三個API的底層其實都是由同一個類實現的:ThreadPoolExecutor

public static ExecutorService newCachedThreadPool() {
  return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                60L, TimeUnit.SECONDS,
                                new SynchronousQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads) {
  return new ThreadPoolExecutor(nThreads, nThreads,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
  return new FinalizableDelegatedExecutorService
    (new ThreadPoolExecutor(1, 1,
                            0L, TimeUnit.MILLISECONDS,
                            new LinkedBlockingQueue<Runnable>()));
}

ThreadPoolExecutor

七大參數

ThreadPoolExecutor類主要有以下七個參數:

  • int corePoolSize: 核心執行緒池大小
  • int maximumPoolSize: 最大核心執行緒池大小
  • long keepAliveTime: 執行緒空閑後的存活時間
  • TimeUnit unit: 超時單位
  • BlockingQueue<Runnable> workQueue: 阻塞隊列
  • ThreadFactory threadFactory: 執行緒工廠:創建執行緒的,一般默認
  • RejectedExecutionHandler handle: 拒絕策略

四種拒絕策略

拒絕策略就是當隊列滿時,執行緒如何去處理新來的任務。

Java內置了四種拒絕策略:

  • ThreadPoolExecutor.CallerRunsPolicy()

  • ThreadPoolExecutor.AbortPolicy()

  • ThreadPoolExecutor.DiscardPolicy()

  • ThreadPoolExecutor.DiscardOldestPolicy()

CallerRunsPolicy(調用者運行策略)

功能:只要執行緒池沒有關閉,就由提交任務的當前執行緒處理。

使用場景:一般在不允許失敗、對性能要求不高、並發量較小的場景下使用。

AbortPolicy(中止策略)

功能:當觸發拒絕策略時,直接拋出拒絕執行的異常

使用場景:ThreadPoolExecutor中默認的策略就是AbortPolicy,由於ExecutorService介面的系列ThreadPoolExecutor都沒有顯示的設置拒絕策略,所以默認的都是這個。

DiscardPolicy(丟棄策略)

功能:直接丟棄這個任務,不觸發任何動作

使用場景:提交的任務無關緊要,一般用的少。

DiscardOldestPolicy(棄老策略)

功能:彈出隊列頭部的元素,然後嘗試執行,相當於排隊的時候把第一個人打死,然後自己代替

使用場景:發布消息、修改消息類似場景。當老消息還未執行,此時新的消息又來了,這時未執行的消息的版本比現在提交的消息版本要低就可以被丟棄了。

執行緒池中的狀態

img

  • RUNNING 自然是運行狀態,指可以接受任務執行隊列里的任務
  • SHUTDOWN 指調用了 shutdown() 方法,不再接受新任務了,但是隊列里的任務得執行完畢。
  • STOP 指調用了 shutdownNow() 方法,不再接受新任務,同時拋棄阻塞隊列里的所有任務並中斷所有正在執行任務。
  • TIDYING 所有任務都執行完畢,在調用 shutdown()/shutdownNow() 中都會嘗試更新為這個狀態。
  • TERMINATED 終止狀態,當執行 terminated() 後會更新為這個狀態。

處理流程

提交一個任務到執行緒池中,執行緒池的處理流程如下:

  • 判斷執行緒池裡的核心執行緒是否都在執行任務

    • 是:進入下個流程。
    • 否:調用/創建一個新的核心執行緒來執行任務。
  • 執行緒池判斷工作隊列是否已滿

    • 是:進入下個流程。
    • 否:將新提交的任務存儲在這個工作隊列里。
  • 判斷執行緒池裡的所有執行緒是否都處於工作狀態

    • 是:交給拒絕策略來處理這個任務。
    • 否:調用/創建一個新的工作執行緒來執行任務。

具體使用

創建

不過最好不要使用Executors來創建執行緒,原因如下(參考自——阿里巴巴Java開發手冊):

  • FixedThreadPoolSingleThreadPool: 允許的請求隊列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM
  • CachedThreadPool: 允許的創建執行緒數量為 Integer.MAX_VALUE,可能會創建大量的執行緒,從而導致 OOM

推薦使用ThreadPoolExecutor類自行創建

// 自定義執行緒池
ExecutorService threadPool = new ThreadPoolExecutor(
  2,
  Runtime.getRuntime().availableProcessors(),//CPU的核心數,適合CPU密集型任務
  3,
  TimeUnit.SECONDS,
  new LinkedBlockingDeque<>(3),
  Executors.defaultThreadFactory(),
  new ThreadPoolExecutor.DiscardOldestPolicy());

合理配置執行緒

執行緒池不是越大越好,要根據任務類型合理進行配置

  • IO 密集型任務:儘可能的多配置執行緒
  • CPU 密集型任務:(大量複雜的運算)應當分配較少的執行緒

執行

有兩個方法可以執行任務executesubmit

  • execute提交沒有返回值,不能判斷是否執行成功。
  • submit會返回一個Future對象,通過Futureget()方法來獲取返回值。

區別:

  • 接收的參數不一樣
    • execute提交的方式只能提交一個Runnable的對象
    • submit有三種
  • submit有返回值,而execute沒有
  • submit方便Exception處理
  • executeExecutor介面中唯一定義的方法
  • submitExecutorService(該介面繼承Executor)中定義的方法

關閉

執行緒池使用完畢,需要對其進行關閉,有兩種方法

  • shutdown():不再繼續接收新的任務,執行完成已有任務後關閉

  • shutdownNow():直接關閉,若果有任務嘗試停止