天天看點

Java線程池參數配置1. 基本概念2. 基本分析法3. 估值計算法

大家好,又見面了,我是你們的朋友全棧君。

線上程池的實際使用中,參數的配置總讓人難以把握。在網上搜了一下,主要有以下的方案。跟大家分享。

1. 基本概念

1.1 ThreadPoolExecutor的重要參數

corePoolSize:核心線程數

  • 核心線程會一直存活,及時沒有任務需要執行
  • 當線程數小于核心線程數時,即使有線程空閑,線程池也會優先建立新線程處理
  • 設定allowCoreThreadTimeout=true(預設false)時,核心線程會逾時關閉

queueCapacity:任務隊列容量(阻塞隊列)

  • 當核心線程數達到最大時,新任務會放在隊列中排隊等待執行

maxPoolSize:最大線程數

  • 當線程數>=corePoolSize,且任務隊列已滿時。線程池會建立新線程來處理任務
  • 當線程數=maxPoolSize,且任務隊列已滿時,線程池會拒絕處理任務而抛出異常

keepAliveTime:線程空閑時間

  • 當線程空閑時間達到keepAliveTime時,線程會退出,直到線程數量=corePoolSize
  • 如果allowCoreThreadTimeout=true,則會直到線程數量=0

allowCoreThreadTimeout:允許核心線程逾時

rejectedExecutionHandler:任務拒絕處理器

  • 兩種情況會拒絕處理任務:
  • 當線程數已經達到maxPoolSize,切隊列已滿,會拒絕新任務
  • 當線程池被調用shutdown()後,會等待線程池裡的任務執行完畢,再shutdown。如果在調用shutdown()和線程池真正shutdown之間送出任務,會拒絕新任務
  • 線程池會調用rejectedExecutionHandler來處理這個任務。如果沒有設定預設是AbortPolicy,會抛出異常

ThreadPoolExecutor類有幾個内部實作類來處理這類情況:

  • AbortPolicy 丢棄任務,抛運作時異常
  • CallerRunsPolicy 執行任務
  • DiscardPolicy 忽視,什麼都不會發生
  • DiscardOldestPolicy 從隊列中踢出最先進入隊列(最後一個執行)的任務

實作RejectedExecutionHandler接口,可自定義處理器

1.2 ThreadPoolExecutor執行順序

  1. 當線程數小于核心線程數時,建立線程。
  2. 當線程數大于等于核心線程數,且任務隊列未滿時,将任務放入任務隊列。
  3. 當線程數大于等于核心線程數,且任務隊列已滿
    1. 若線程數小于最大線程數,建立線程
    2. 若線程數等于最大線程數,抛出異常,拒絕任務

2. 基本分析法

要想合理的配置線程池,就必須首先分析任務特性,可以從以下幾個角度來進行分析:

  1. 任務的性質:CPU密集型任務,IO密集型任務和混合型任務。
  2. 任務的優先級:高,中和低。
  3. 任務的執行時間:長,中和短。
  4. 任務的依賴性:是否依賴其他系統資源,如資料庫連接配接。

2.1 任務性質不同的任務

任務性質不同的任務可以用不同規模的線程池分開處理。

2.1.1 CPU密集型任務

CPU密集型任務配置盡可能少的線程數量,如配置cpu核數+1個線程能夠實作最優的CPU使用率,+1是保證當線程由于頁缺失故障(作業系統)或其它原因導緻暫停時,額外的這個線程就能頂上去,保證CPU的時鐘周期不被浪費。

2.1.2 IO密集型任務

IO密集型任務則由于需要等待IO操作,CPU不總是處于繁忙狀态。則配置盡可能多的線程,利用多線程提高CPU的使用率。經驗公式如下:

線程數 = 核數 * 期望 CPU 使用率 * 總時間(CPU計算時間+等待時間) / CPU 計算時間

例如 4 核 CPU 計算時間是 50% ,其它等待時間是 50%,期望 cpu 被 100% 利用,

套用公式 4 * 100% * 100% / 50% = 8

例如 4 核 CPU 計算時間是 10% ,其它等待時間是 90%,期望 cpu 被 100% 利用,

套用公式 4 * 100% * 100% / 10% = 40

2.1.3 混合型的任務

混合型的任務,如果可以拆分,則将其拆分成一個CPU密集型任務和一個IO密集型任務,隻要這兩個任務執行的時間相差不是太大,那麼分解後執行的吞吐率要高于串行執行的吞吐率,如果這兩個任務執行時間相差太大,則沒必要進行分解。我們可以通過Runtime.getRuntime().availableProcessors()方法獲得目前裝置的CPU個數。

2.2 優先級不同的任務

優先級不同的任務可以使用優先級隊列PriorityBlockingQueue來處理。它可以讓優先級高的任務先得到執行,需要注意的是如果一直有優先級高的任務送出到隊列裡,那麼優先級低的任務可能永遠不能執行。

2.3 執行時間不同的任務

執行時間不同的任務可以交給不同規模的線程池來處理,或者也可以使用優先級隊列,讓執行時間短的任務先執行。

2.4 依賴資料庫連接配接池的任務

依賴資料庫連接配接池的任務,因為線程送出SQL後需要等待資料庫傳回結果,如果等待的時間越長CPU空閑時間就越長,那麼線程數應該設定越大,這樣才能更好的利用CPU。

并且,阻塞隊列最好是使用有界隊列,如果采用無界隊列的話,一旦任務積壓在阻塞隊列中的話就會占用過多的記憶體資源,甚至會使得系統崩潰。

3. 估值計算法

3.1 預設值

  • corePoolSize=1
  • queueCapacity=Integer.MAX_VALUE
  • maxPoolSize=Integer.MAX_VALUE
  • keepAliveTime=60s
  • allowCoreThreadTimeout=false
  • rejectedExecutionHandler=AbortPolicy()

3.2 如何來設定

需要根據幾個值來決定:

  • tasks :每秒的任務數,假設為500~1000
  • taskcost:每個任務花費時間,假設為0.1s
  • responsetime:系統允許容忍的最大響應時間,假設為1s

做幾個計算:

corePoolSize = 每秒需要多少個線程處理?

  • threadcount = tasks/(1/taskcost) =tasks*taskcout = (500~1000)*0.1 = 50~100 個線程。corePoolSize設定應該大于50
  • 根據8020原則,如果80%的每秒任務數小于800,那麼corePoolSize設定為80即可

queueCapacity = (coreSizePool/taskcost)*responsetime

  • 計算可得 queueCapacity = 80/0.1*1 = 80。意思是隊列裡的線程可以等待1s,超過了的需要新開線程來執行
  • 切記不能設定為Integer.MAX_VALUE,這樣隊列會很大,線程數隻會保持在corePoolSize大小,當任務陡增時,不能新開線程來執行,響應時間會随之陡增。

maxPoolSize = (max(tasks)- queueCapacity)/(1/taskcost)

  • 計算可得 maxPoolSize = (1000-80)/10 = 92
  • (最大任務數-隊列容量)/每個線程每秒處理能力 = 最大線程數

rejectedExecutionHandler:根據具體情況來決定,任務不重要可丢棄,任務重要則要利用一些緩沖機制來處理

keepAliveTime和allowCoreThreadTimeout采用預設通常能滿足

以上都是理想值,實際情況下要根據機器性能來決定。如果在未達到最大線程數的情況機器cpu load已經滿了,則需要通過更新硬體(呵呵)和優化代碼,降低taskcost來處理。

釋出者:全棧程式員棧長,轉載請注明出處:https://javaforall.cn/142742.html原文連結:https://javaforall.cn