1. 程式人生 > 其它 >執行緒池之ThreadPoolExecutor概述

執行緒池之ThreadPoolExecutor概述

執行緒池解決了兩個不同的問題:

  • 提升效能:它們通常在執行大量非同步任務時,由於減少了每個任務的呼叫開銷,並且它們提供了一種限制和管理資源(包括執行緒)的方法,使得效能提升明顯;
  • 統計資訊:每個ThreadPoolExecutor保持一些基本的統計資訊,例如完成的任務數量。

為了在廣泛的上下文中有用,此類提供了許多可調引數和可擴充套件性鉤子。 但是,在常見場景中,我們預配置了幾種執行緒池,我們敦促程式設計師使用更方便的Executors的工廠方法直接使用。

  • Executors.newCachedThreadPool(無界執行緒池,自動執行緒回收)
  • Executors.newFixedThreadPool(固定大小的執行緒池);
  • Executors.newSingleThreadExecutor(單一後臺執行緒);
注:這裡沒有提到ScheduledExecutorService ,後續解析。

一、Core and maximum pool sizes 核心和最大執行緒池數量

引數翻譯
corePoolSize 核心執行緒池數量
maximumPoolSize 最大執行緒池數量

執行緒池執行器將會根據corePoolSize和maximumPoolSize自動地調整執行緒池大小。

當在execute(Runnable)方法中提交新任務並且少於corePoolSize執行緒正在執行時,即使其他工作執行緒處於空閒狀態,也會建立一個新執行緒來處理該請求。
如果有多於corePoolSize但小於maximumPoolSize執行緒正在執行,則僅當佇列已滿時才會建立新執行緒。 通過設定corePoolSize和maximumPoolSize相同,您可以建立一個固定大小的執行緒池。 通過將maximumPoolSize設定為基本上無界的值,例如Integer.MAX_VALUE,您可以允許池容納任意數量的併發任務。 通常,核心和最大池大小僅在構建時設定,但也可以使用setCorePoolSizesetMaximumPoolSize進行動態更改。 這段話詳細了描述了執行緒池對任務的處理流程,這裡用個圖總結一下

二、prestartCoreThread 核心執行緒預啟動

在預設情況下,只有當新任務到達時,才開始建立和啟動核心執行緒, 但是我們可以使用 prestartCoreThread()prestartAllCoreThreads() 方法動態調整。
如果使用非空佇列構建池,則可能需要預先啟動執行緒
方法作用
prestartCoreThread() 創一個空閒任務執行緒等待任務的到達
prestartAllCoreThreads() 建立核心執行緒池數量的空閒任務執行緒等待任務的到達

三、ThreadFactory 執行緒工廠

新執行緒使用ThreadFactory建立。 如果未另行指定,則使用Executors.defaultThreadFactory預設工廠,使其全部位於同一個ThreadGroup中,並且具有相同的NORM_PRIORITY優先順序和非守護程序狀態。 通過提供不同的ThreadFactory,您可以更改執行緒的名稱,執行緒組,優先順序,守護程序狀態等。

四、Keep-alive times 執行緒存活時間

如果執行緒池當前擁有超過corePoolSize的執行緒,那麼多餘的執行緒在空閒時間超過keepAliveTime時會被終止 ( 請參閱getKeepAliveTime(TimeUnit) )。 這提供了一種在不積極使用執行緒池時減少資源消耗的方法。 如果池在以後變得更加活躍,則應構建新執行緒。 也可以使用方法setKeepAliveTime(long,TimeUnit)進行動態調整。

防止空閒執行緒在關閉之前終止,可以使用如下方法:

setKeepAliveTime(Long.MAX_VALUE,TimeUnit.NANOSECONDS);
預設情況下,keep-alive策略僅適用於存在超過corePoolSize執行緒的情況。 但是,只要keepAliveTime值不為零,方法allowCoreThreadTimeOut(boolean)也可用於將此超時策略應用於核心執行緒

五、Queuing 佇列

BlockingQueu用於存放提交的任務,佇列的實際容量與執行緒池大小相關聯。

  • 如果當前執行緒池任務執行緒數量小於核心執行緒池數量,執行器總是優先建立一個任務執行緒而不是從執行緒佇列中取一個空閒執行緒。

  • 如果當前執行緒池任務執行緒數量大於核心執行緒池數量,執行器總是優先從執行緒佇列中取一個空閒執行緒而不是建立一個任務執行緒。

  • 如果當前執行緒池任務執行緒數量大於核心執行緒池數量,且佇列中無空閒任務執行緒,將會建立一個任務執行緒,直到超出maximumPoolSize如果超時maximumPoolSize,則任務將會被拒絕

主要有三種佇列策略:
  • Direct handoffs 直接握手佇列
  • Unbounded queues 無界佇列
  • Bounded queues 有界佇列

六、Rejected tasks 拒絕任務

拒絕任務有兩種情況:
  1. 執行緒池已經被關閉;
  2. 任務佇列已滿且maximumPoolSizes已滿;
無論哪種情況,都會呼叫RejectedExecutionHandler的rejectedExecution方法。預定義了四種處理策略
  • AbortPolicy:預設測策略,丟擲RejectedExecutionException執行時異常;
  • CallerRunsPolicy:這提供了一個簡單的反饋控制機制,可以減慢提交新任務的速度;
  • DiscardPolicy:直接丟棄新提交的任務;
  • DiscardOldestPolicy:如果執行器沒有關閉,佇列頭的任務將會被丟棄,然後執行器重新嘗試執行任務(如果失敗,則重複這一過程);
    我們可以自己定義RejectedExecutionHandler,以適應特殊的容量和佇列策略場景中。

七、Hook methods 鉤子方法

ThreadPoolExecutor為提供了每個任務執行前後提供了鉤子方法, 重寫beforeExecute(Thread,Runnable)afterExecute(Runnable,Throwable)方法來操縱執行環境; 例如,重新初始化ThreadLocals,收集統計資訊或記錄日誌等。 此外,terminated()在Executor完全終止後需要完成後會被呼叫,可以重寫此方法,以執行任殊處理。
注意:如果hook或回撥方法丟擲異常,內部的任務執行緒將會失敗並結束

八、Queue maintenance 維護佇列

getQueue()方法可以訪問任務佇列,一般用於監控和除錯。 絕不建議將這個方法用於其他目的。當在大量的佇列任務被取消時,remove()purge()方法可用於回收空間。

九、Finalization 關閉

如果程式中不在持有執行緒池的引用,並且執行緒池中沒有執行緒時,執行緒池將會自動關閉。 如果您希望確保即使使用者忘記呼叫 shutdown()方法也可以回收未引用的執行緒池,使未使用執行緒最終死亡。 那麼必須通過設定適當的 keep-alive times 並設定allowCoreThreadTimeOut(boolean) 或者 使 corePoolSize下限為0 。
一般情況下,執行緒池啟動後建議手動呼叫shutdown()關閉。

https://www.jianshu.com/p/c41e942bcd64