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

阿墩發表於2021-01-17

執行緒池的優點

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

執行緒池的實現原理

池化技術

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

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

常見池化技術的例如:

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

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():直接關閉,若果有任務嘗試停止

相關文章