ThreadPoolExecutor線程池參數設置技巧


ThreadPoolExecutor線程池參數設置技巧

理解ThreadPoolExecutor線程池的corePoolSize、maximumPoolSize和poolSize

我們知道,受限於硬件、內存和性能,我們不可能無限制的創建任意數量的線程,因為每一台機器允許的最大線程是一個有界值。也就是說ThreadPoolExecutor管理的線程數量是有界的。線程池就是用這些有限個數的線程,去執行提交的任務。然而對於多用戶、高並發的應用來說,提交的任務數量非常巨大,一定會比允許的最大線程數多很多。為了解決這個問題,必須要引入排隊機制,或者是在內存中,或者是在硬盤等容量很大的存儲介質中。J.U.C提供的ThreadPoolExecutor只支持任務在內存中排隊,通過BlockingQueue暫存還沒有來得及執行的任務。

任務的管理是一件比較容易的事,復雜的是線程的管理,這會涉及線程數量、等待/喚醒、同步/鎖、線程創建和死亡等問題。ThreadPoolExecutor與線程相關的幾個成員變量是:keepAliveTime、allowCoreThreadTimeOut、poolSize、corePoolSize、maximumPoolSize,它們共同負責線程的創建和銷毀。

 

corePoolSize:

線程池的基本大小,即在沒有任務需要執行的時候線程池的大小,並且只有在工作隊列滿了的情況下才會創建超出這個數量的線程。這里需要注意的是:在剛剛創建ThreadPoolExecutor的時候,線程並不會立即啟動,而是要等到有任務提交時才會啟動,除非調用了prestartCoreThread/prestartAllCoreThreads事先啟動核心線程。再考慮到keepAliveTime和allowCoreThreadTimeOut超時參數的影響,所以沒有任務需要執行的時候,線程池的大小不一定是corePoolSize。

 

maximumPoolSize:

線程池中允許的最大線程數,線程池中的當前線程數目不會超過該值。如果隊列中任務已滿,並且當前線程個數小於maximumPoolSize,那么會創建新的線程來執行任務。這里值得一提的是largestPoolSize,該變量記錄了線程池在整個生命周期中曾經出現的最大線程個數。為什么說是曾經呢?因為線程池創建之后,可以調用setMaximumPoolSize()改變運行的最大線程的數目。

 

poolSize:

線程池中當前線程的數量,當該值為0的時候,意味着沒有任何線程,線程池會終止;同一時刻,poolSize不會超過maximumPoolSize。

現在我們通過ThreadPoolExecutor.execute()方法,看一下這3個屬性的關系,以及線程池如何處理新提交的任務。以下源碼基於JDK1.6.0_37版本。

新提交一個任務時的處理流程很明顯:

1、如果當前線程池的線程數還沒有達到基本大小(poolSize < corePoolSize),無論是否有空閑的線程新增一個線程處理新提交的任務;

2、如果當前線程池的線程數大於或等於基本大小(poolSize >= corePoolSize) 且任務隊列未滿時,就將新提交的任務提交到阻塞隊列排隊,等候處理workQueue.offer(command);

3、如果當前線程池的線程數大於或等於基本大小(poolSize >= corePoolSize) 且任務隊列滿時;

3.1、當前poolSize<maximumPoolSize,那么就新增線程來處理任務;

3.2、當前poolSize=maximumPoolSize,那么意味着線程池的處理能力已經達到了極限,此時需要拒絕新增加的任務。至於如何拒絕處理新增的任務,取決於線程池的飽和策略RejectedExecutionHandler。

   1、corePoolSize:核心線程數
        * 核心線程會一直存活,及時沒有任務需要執行
        * 當線程數小於核心線程數時,即使有線程空閑,線程池也會優先創建新線程處理
        * 設置allowCoreThreadTimeout=true(默認false)時,核心線程會超時關閉

    2、queueCapacity:任務隊列容量(阻塞隊列)
        * 當核心線程數達到最大時,新任務會放在隊列中排隊等待執行

    3、maxPoolSize:最大線程數
        * 當線程數>=corePoolSize,且任務隊列已滿時。線程池會創建新線程來處理任務
        * 當線程數=maxPoolSize,且任務隊列已滿時,線程池會拒絕處理任務而拋出異常

    4、 keepAliveTime:線程空閑時間
        * 線程數>=corePoolSize時,當線程空閑時間達到keepAliveTime時,線程會退出,直到線程數量=corePoolSize; 
     * 如果allowCoreThreadTimeout=true,則會直到線程數量=0

   5、allowCoreThreadTimeout:允許核心線程超時
     * 線程數<=corePoolSize時.該屬性用來控制是否允許核心線程超時退出
   6、rejectedExecutionHandler:任務拒絕處理器 
    * 兩種情況會拒絕處理任務:
      - 當線程數已經達到maxPoolSize,切隊列已滿,會拒絕新任務
      - 當線程池被調用shutdown()后,會等待線程池里的任務執行完畢,再shutdown。如果在調用shutdown()和線程池真正shutdown之間提交任務,會拒絕新任務
    * 線程池會調用rejectedExecutionHandler來處理這個任務。如果沒有設置默認是AbortPolicy,會拋出異常
    * ThreadPoolExecutor類有幾個內部實現類來處理這類情況:
      - AbortPolicy 丟棄任務,拋運行時異常 - CallerRunsPolicy 執行任務
      - DiscardPolicy 忽視,什么都不會發生 - DiscardOldestPolicy 從隊列中踢出最先進入隊列(最后一個執行)的任務
    * 實現RejectedExecutionHandler接口,可自定義處理器

allowCoreThreadTimeOut和keepAliveTime屬性的含義。

在壓力很大的情況下,線程池中的所有線程都在處理新提交的任務或者是在排隊的任務,這個時候線程池處在忙碌狀態。如果壓力很小,那么可能很多線程池都處在空閑狀態,這個時候為了節省系統資源,回收這些沒有用的空閑線程,就必須提供一些超時機制,這也是線程池大小調節策略的一部分。通過corePoolSize和maximumPoolSize,控制如何新增線程;通過allowCoreThreadTimeOut和keepAliveTime,控制如何銷毀線程。

 

allowCoreThreadTimeOut:

該屬性用來控制是否允許核心線程超時退出。默認值為:false。If false,core threads stay alive even when idle.If true, core threads use keepAliveTime to time out waiting for work。如果線程池的大小已經達到了corePoolSize,不管有沒有任務需要執行,線程池都會保證這些核心線程處於存活狀態。可以知道:該屬性只是用來控制核心線程的。

 

keepAliveTime:

如果一個線程處在空閑狀態的時間超過了該屬性值,就會因為超時而退出。舉個例子,如果線程池的核心大小corePoolSize=5,而當前大小poolSize =8,那么超出核心大小的線程,會按照keepAliveTime的值判斷是否會超時退出。如果線程池的核心大小corePoolSize=5,而當前大小poolSize =5,那么線程池中所有線程都是核心線程,這個時候線程是否會退出,取決於allowCoreThreadTimeOut。

阻塞隊列

ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;
PriorityBlockingQueue 

排隊

  所有BlockingQueue都可用於傳輸和保持提交的任務。可以使用此隊列與池大小進行交互:
  如果運行的線程少於corePoolSize,則Executor始終首選添加新的線程,而不進行排隊。
  如果運行的線程等於或多於corePoolSize,則Executor始終首選將請求加入隊列,而不添加新的線程。
  如果無法將請求加入隊列,則創建新的線程,除非創建此線程超出maximumPoolSize,在這種情況下,任務將被拒絕(拋出RejectedExecutionException)。
排隊有三種通用策略:
1 直接提交。

  工作隊列的默認選項是synchronousQueue,它將任務直接提交給線程而不保持它們。在此,如果不存在可用於立即運行任務的線程,則試圖把任務加入隊列將失敗,因此會構造一個新的線程。此策略可以避免在處理可能具有內部依賴性的請求集時出現鎖。直接提交通常要求無界maximumPoolSizes以避免拒絕新提交的任務。當命令以超過隊列所能處理的平均數連續到達時,此策略允許無界線程具有增加的可能性。
2 無界隊列。

  使用無界隊列(例如,不具有預定義容量的LinkedBlockingQueue)將導致在所有corePoolSize線程都忙時新任務在隊列中等待。這樣,創建的線程就不會超過corePoolSize(因此,maximumPoolSize的值也就無效了)。
3 有界隊列。

  當使用有限的maximumPoolSizes時,有界隊列(如ArrayBlockingQueue)有助於防止資源耗盡,但是可能較難調整和控制。隊列大小和最大池大小可能需要相互折衷:使用大型隊列和小型池可以最大限度的降低CPU使用率、操作系統資源和上下文切換開銷,但是可能導致人工降低吞吐量。如果任務頻繁阻塞,則系統可能為超過您許可的更多線程安排時間,使用小型隊列通常要求較大的池大小,CPU使用率較高,但是可能遇到不可接受的調度開銷,這樣可會降低吞吐量。


終止:程序不再引用的池沒有剩余線程會自動shutdown。如果希望確保回收取消引用的池(即使用戶忘記調用shutdown()),則必須安排未使用的線程最終終止。

 

https://blog.csdn.net/wzy_1988/article/details/38922449


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM