1. 程式人生 > >執行緒池種類以及引數設定問題

執行緒池種類以及引數設定問題

JDK1.5中引入了強大的concurrent包,執行緒池的實現ThreadPoolExcutor。

執行緒池種類

    通常開發者都是利用Executors提供的通用執行緒池建立方法,去建立不同配置的執行緒池,主要區別在於ExecutorService型別或者不同的初始引數。

    Executors目前提供了五種不同的執行緒池建立配置:

  • newCachedThreadPool(),它是一種用來處理大量短時間工作任務的執行緒池,具有幾個鮮明特點:它會試圖快取執行緒並重用,當無快取執行緒可用時,就會建立新的工作執行緒;如果執行緒閒置的時間超過60秒,則被終止並移出快取;長時間閒置時,這種執行緒池並不會消耗什麼資源,其內部使用SynchronousQueue作為工作佇列;
  • newFixedThreadPool(),建立固定大小的執行緒池,背後使用的是無界的工作佇列,任何時候最多有nThreads個工作執行緒是活動的。這意味著,如果任務數量超過了活動佇列數目,將在工作佇列中等待空閒執行緒出現;如果有工作執行緒退出,將會有新的工作執行緒被建立,以補足指定的數目nThreads。
  • newSingleThreadExcuteor(),它的特點在於工作執行緒數目被限制為1,操作一個無界的工作佇列,所以保證了所有任務都是被順序執行的,最多隻會有一個任務處於活動狀態,並且不允許使用者改動執行緒池例項,因此可以避免其改變執行緒數目。
  • newSingleThreadScheduledExcutor()和newScheduleThreadPool(int corePoolSize),建立的是個ScheduledExcutorService,可以進行定時或週期性的工作排程,區別在於單一工作執行緒還是多個工作執行緒。
  • newWorkStealingPool(int parallelism),這是一個經常被人忽略的執行緒池,Java 8才加入這個建立方法,其內部會構建ForkJoinPool,利用Work-Stealing演算法,並行地處理任務,不保證處理順序。

阻塞佇列(runnableTaskQueue)

  • ArrayBlockingQueue:一個基於陣列結構的有界阻塞佇列,此佇列按照先進先出原則對元素進行排序;
  • LinkedBlockingQueue:一個基於連結串列結構的阻塞佇列,按照先進先出對元素進行排序。吞吐量通常要高於ArrayBlockingQueue。靜態工廠方法Excutors.newFixedThreadPool()使用了這個佇列;
  • SynchronousQueue:一個不儲存元素的阻塞佇列。每個插入操作必須等到另一個執行緒呼叫移除操作,否則插入操作一直處於阻塞狀態。吞吐量要高於LinkedBlockingQueue。靜態工廠方法Excutors.newCachedThreadPool()使用了這個佇列;
  • PriorityBlockingQueue:一個具有優先順序的無界阻塞佇列。

系統負載

    引數的設定跟系統的負載有直接的關係,系統負載相關引數:

  • tasks,每秒需要處理的最大任務數
  • tasktime,處理每個任務所需要的時間
  • responsetime,系統允許任務最大的響應時間,比如每個任務的響應時間不得超過2秒

ThreadPoolExcutor類可設定的引數有:

1.corePoolSize:

    核心執行緒數,核心執行緒會一直存活,即使沒有任務需要處理。當執行緒數小於核心執行緒數時,即使現有的執行緒空閒,執行緒池也會優先建立新執行緒來處理任務,而不是直接交給現有的執行緒處理;

    核心執行緒在allowCoreThreadTimeout被設定為true時會超時退出,預設情況下不會退出。

    每個任務需要tasktime秒處理,則每個執行緒每秒可以處理1/tasktime個任務。系統每秒有tasks個任務,所以需要的執行緒數為tasks/(1/tasktime),即tasks*tasktime個執行緒數。

    假設系統每秒任務數為100-1000,每個任務耗時0.1秒,那麼需要10-100個執行緒, 核心執行緒數應設定為大於10,具體數字最好根據8020原則,即80%情況下系統每秒任務數,若系統80%情況下每秒任務數小於200,最多時為1000,那麼可以設定為20。

2.maxPoolSize:

    當執行緒數大於或等於核心執行緒,且任務佇列已滿時,執行緒池會建立新的執行緒,直到執行緒數量達到maxPoolSize。如果執行緒數等於最大執行緒數,則已經超出執行緒池的處理能力,執行緒池會拒絕處理任務而丟擲異常。

    當系統負載達到最大值的時候,核心執行緒數已經無法按時處理完所有任務,這時候就需要增加執行緒。每秒200個任務需要20個執行緒,當每秒達到1000個任務的時候,需要(1000-queueCapacity)*(20/200)=60個執行緒,可以將最大執行緒數設定為60。

3.keepAliveTime:

    當執行緒空閒時間達到keepAliveTime,該執行緒會退出,直到執行緒數量達到corePoolSize。如果allowCoreThreadTimeout被設定為true,則所有執行緒均會退出直到執行緒數量為0.

4.allowCoreThreadTimeout:

    是否允許核心執行緒空閒退出。

5.queueCapacity:

    任務佇列容量。從maxPoolSize的描述上可以看出,任務佇列的容量會影響到執行緒的變化,因此任務佇列的長度也需要恰當的設定。

    任務佇列的長度要根據核心執行緒數,以及系統對任務響應時間的要求有關。佇列長度可以設定為:

(corePoolSize/tasktime)*responsetime:20/0.1*2=400,即佇列長度可以設定為400。

    佇列長度設定過大,會導致任務響應時間過長,切忌以下寫法:

LinkedBlockingQueue queue=new LinkedBlockingQueue();

    這實際上是將佇列長度設定為Integer.MAX_VALUE,將會導致執行緒數量永遠為corePoolSize,再也不會增加,當任務數量陡增時,任務響應時間也隨之陡增。

6.RejectedExcutionHandler:飽和策略

當佇列和執行緒池都滿了,說明執行緒池處於飽和狀態,那麼必須對新提交的任務採用一種特殊的策略來進行處理。這個策略預設配置是AbortPolicy,表示無法處理新的任務而丟擲異常。JAVA提供了4中策略:

  • AbortPolicy:直接丟擲異常
  • CallerRunsPolicy:只用呼叫所在的執行緒執行任務
  • DiscardOledestPolicy:丟棄佇列裡最近的一個任務,並執行當前任務
  • DiscardPolicy:不處理,丟棄掉

    以上關於執行緒數量的計算並沒有考慮CPU的情況。若結合CPU的情況,比如,當執行緒數量達到50時,CPU達到100%,則將maxPoolSize設定為60也不合適,此時若系統負載長時間維持在每秒1000個任務,則超出執行緒池處理能力,應設法降低每個任務的處理時間(tasktime)。