1.先了解執行緒池的幾個引數含義
corePoolSize
(核心執行緒池大小):
- 作用: 指定了執行緒池維護的核心執行緒數量,即使這些執行緒處於空閒狀態,它們也不會被回收。
- 用途: 核心執行緒用於處理長期的任務,保持最低的執行緒數量,以減少執行緒的建立和銷燬的開銷。
maximumPoolSize
(最大執行緒池大小):
- 作用: 指定了執行緒池中允許的最大執行緒數。超過這個數量的執行緒將不會被建立。
- 用途: 限制了執行緒池的大小,以防止資源耗盡。
keepAliveTime
(執行緒空閒時間):
- 作用: 當執行緒數超過
corePoolSize
時,多餘的執行緒在空閒時間超過指定時間後將會被終止和回收。 - 用途: 用於回收不再需要的執行緒,降低資源消耗。只對超過
corePoolSize
的執行緒起作用。
unit
(時間單位):
- 作用: 與
keepAliveTime
一起使用,指定執行緒空閒時間的時間單位(如秒、毫秒)。 - 用途: 定義
keepAliveTime
的時間單位。
workQueue
(任務佇列):
-
作用: 用於儲存等待執行的任務的佇列。
-
- 用途
- 管理任務的排隊和處理方式,不同的佇列型別可以影響執行緒池的行為。
- 常見的佇列型別有:
SynchronousQueue
: 不儲存任務,任務直接交給執行緒執行。如果沒有空閒執行緒,則建立新執行緒。LinkedBlockingQueue
: 無界佇列,可以儲存任意多的任務。只有在任務佇列為空時,才會建立新執行緒。ArrayBlockingQueue
: 有界佇列,儲存固定數量的任務,當佇列滿時,任務將被拒絕。
threadFactory
(執行緒工廠):
- 作用: 用於建立執行緒的工廠,可以定製執行緒的建立,比如設定執行緒名、優先順序等。
- 用途: 統一管理執行緒的建立細節,有助於除錯和監控。
handler
(飽和策略/拒絕策略):
-
作用: 當任務無法提交給執行緒池(例如執行緒池已滿且任務佇列已滿)時,如何處理新任務。
-
- 用途
- 定義任務無法被執行時的處理方式。
- 常見策略有:
AbortPolicy
: 丟擲RejectedExecutionException
異常。CallerRunsPolicy
: 由呼叫者執行緒執行該任務。DiscardPolicy
: 丟棄新提交的任務。DiscardOldestPolicy
: 丟棄佇列中最舊的任務。
2.調整執行緒池配置應對高併發(常規操作)
為了應對高併發的需求,可以考慮以下調整:
- 增大
corePoolSize
和maximumPoolSize
:- 增加核心執行緒和最大執行緒數可以提高執行緒池的併發處理能力,減少任務的等待時間。
- 調整
keepAliveTime
和unit
:- 減少
keepAliveTime
可以更快地回收閒置執行緒,釋放資源。相反,增加keepAliveTime
適用於任務間隔較長的場景,以避免頻繁建立和銷燬執行緒。
- 減少
- 選擇合適的
workQueue
:- 使用
SynchronousQueue
可以在任務很多但執行緒數不足時迅速增加執行緒數。 - 使用
LinkedBlockingQueue
可以應對任務佇列過長的問題,但可能導致執行緒數不會增加到最大。 - 使用
ArrayBlockingQueue
適合在任務數有限的場景,防止資源耗盡。
- 使用
- 合理配置
handler
:- 根據系統需求選擇適合的拒絕策略。比如,在希望任務儘量被處理時使用
CallerRunsPolicy
,在任務不能丟失時選擇AbortPolicy
。
- 根據系統需求選擇適合的拒絕策略。比如,在希望任務儘量被處理時使用
- 最佳化
threadFactory
:- 使用自定義的執行緒工廠設定執行緒名、優先順序、守護執行緒等,提高執行緒管理的清晰度和系統穩定性。
- 監控和調整:
- 定期監控執行緒池的效能指標,如任務佇列長度、執行緒使用率等,並根據實際情況動態調整引數配置。
// 建立執行緒池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
10, // corePoolSize
50, // maximumPoolSize
60, // keepAliveTime
TimeUnit.SECONDS, // keepAliveTime's unit
new LinkedBlockingQueue<>(100), // workQueue
Executors.defaultThreadFactory(), // threadFactory
new ThreadPoolExecutor.AbortPolicy() // handler
);
// 提交任務
executor.submit(() -> {
// Task implementation
});
// 關閉執行緒池
executor.shutdown();
3.IO密集型、CPU密集型任務的合理配置(生產常用)
3.1 IO密集型任務
IO密集型任務:(例如網路操作、檔案讀寫)通常不需要大量的CPU時間,但可能會等待IO操作的完成。為了有效利用系統資源,可以配置
更多
的執行緒來掩蓋IO操作的等待時間。
配置建議:
corePoolSize
和maximumPoolSize
:- 建議的執行緒數通常遠超過 CPU 核心數,因為執行緒在等待IO操作時不會佔用CPU。可以使用
(CPU 核心數 * 2)
或更多,甚至是(CPU 核心數 * 2) + 1
這種經驗值。 - 如果執行緒數太少,CPU資源可能未能充分利用。太多的執行緒可能會導致執行緒上下文切換的開銷。
- 建議的執行緒數通常遠超過 CPU 核心數,因為執行緒在等待IO操作時不會佔用CPU。可以使用
keepAliveTime
和unit
:- 適當地增加
keepAliveTime
,讓執行緒在空閒時保留一段時間,以便在短時間內有任務到達時無需重新建立執行緒。
- 適當地增加
workQueue
:LinkedBlockingQueue
是常見選擇,因為它可以有效處理大量任務,而不需要頻繁地建立和銷燬執行緒。SynchronousQueue
也可以用於高併發IO場景,確保任務直接交給執行緒執行,迅速響應。
示例:
int numCores = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor ioBoundExecutor = new ThreadPoolExecutor(
numCores * 2, // corePoolSize
numCores * 2 + 1, // maximumPoolSize
60L, // keepAliveTime
TimeUnit.SECONDS, // keepAliveTime's unit
new LinkedBlockingQueue<>(), // workQueue
Executors.defaultThreadFactory(), // threadFactory
new ThreadPoolExecutor.CallerRunsPolicy() // handler
);
3.2 CPU密集型任務
CPU密集型任務:(例如計算密集的操作、資料處理)主要消耗
CPU 資源
,因此執行緒數應該與 CPU 核心數相匹配,以避免過度的執行緒上下文切換和資源競爭。
配置建議:
corePoolSize
和maximumPoolSize
:- 通常設定為
CPU 核心數
或CPU 核心數 + 1
。 - 過多的執行緒可能導致頻繁的上下文切換,降低效能。
- 通常設定為
keepAliveTime
和unit
:keepAliveTime
通常設定較短,適合及時回收空閒執行緒。
workQueue
:SynchronousQueue
或ArrayBlockingQueue
是不錯的選擇,可以避免任務堆積,確保執行緒數控制在合理範圍內。
示例:
int numCores = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor cpuBoundExecutor = new ThreadPoolExecutor(
numCores, // corePoolSize
numCores + 1, // maximumPoolSize
30L, // keepAliveTime
TimeUnit.SECONDS, // keepAliveTime's unit
new SynchronousQueue<>(), // workQueue
Executors.defaultThreadFactory(), // threadFactory
new ThreadPoolExecutor.AbortPolicy() // handler
);
3.3 關鍵考慮因素
- 系統資源和負載:
- 監控系統的實際負載和資源使用情況,定期調整配置。
- 任務特性:
- 根據任務的性質(長任務、短任務、IO 密集型、CPU 密集型)選擇合適的執行緒池配置。
- 阻塞時間:
- 對於 IO 密集型任務,理解和分析任務的阻塞時間,並根據其阻塞時間設定合適的執行緒池大小。
- 拒絕策略:
- 合理選擇拒絕策略(如
AbortPolicy
,CallerRunsPolicy
),確保系統在負載過高時能平穩處理任務。
- 合理選擇拒絕策略(如
4.專業級執行緒池配置(大廠規範)
4.1 執行緒池大小的計算公式
IO 密集型任務
對於IO密集型任務,可以使用以下公式計算適合的執行緒池大小:
N_threads
: 推薦的執行緒池大小N_cores
: CPU核心數W
: 任務的等待時間(包括IO操作的等待時間)C
: 任務的計算時間U
: 期望的CPU使用率,通常設為0.8~0.9,避免CPU負載過高(0 < U < 1)解釋: 公式中的 W/C反映了IO操作佔用的時間比,
1 - U
是為了預留一定的CPU資源。
示例:
假設有一個任務,CPU核心數為8,IO等待時間為200ms,計算時間為100ms,期望的CPU使用率為80%,則推薦的執行緒池大小為:
這意味著你可能需要配置大約120個執行緒來處理IO密集型任務。
CPU 密集型任務
對於CPU密集型任務,執行緒池的大小通常可以透過以下公式估算:
在CPU密集型場景下,由於
W
很小或接近於零,因此公式通常簡化為:
示例:
假設有一個任務,CPU核心數為8,計算時間大部分佔用時間,等待時間可以忽略不計,則推薦的執行緒池大小為:
5.根據TPS和QPS進行執行緒池計算(生產常用)
其實和4的公式差不多
5.1 基礎概念:
TPS (Transactions Per Second)
: 每秒系統處理的事務
數量。這通常用於描述系統處理更復雜的業務邏輯的能力。QPS (Queries Per Second)
: 每秒系統處理的查詢
數量,通常用於衡量服務端API或資料庫的查詢處理能力。響應時間
: 單個請求或事務的平均處理時間。
5.2 公式:
N_threads
: 推薦的執行緒池大小Q
: 每秒的請求數(TPS 或 QPS)R
: 平均響應時間(秒)U
: 系統期望的CPU利用率(< 1, 通常為80%~90%)
解釋: 公式描述了在滿足特定吞吐量和響應時間的情況下,需要的執行緒數,預留了一部分CPU資源以防過載。
5.3 IO密集型、CPU密集型任務選擇
這裡我們主要舉例說明IO密集型任務
因為:
CPU密集型
任務主要消耗CPU資源,執行緒數接近CPU核心數就足夠,可以加一個額外的執行緒來處理。Nthreads=Ncores+1
IO密集型:
公式:
說明: 由於IO密集型任務在等待IO時不會佔用CPU,因此執行緒數可以較高,適用於處理高併發的IO操作。
示例:
假設系統需要處理每秒500個請求(Q = 500),每個請求的平均響應時間為0.2秒,系統期望的CPU利用率為80%(U = 0.8):
這意味著你可能需要大約500個執行緒來處理這些IO密集型請求。
示例程式碼:
int qps = 500;
double responseTime = 0.2;
double targetUtilization = 0.8;
int nThreads = (int) (qps * responseTime / (1 - targetUtilization));
ThreadPoolExecutor ioBoundExecutor = new ThreadPoolExecutor(
nThreads, // corePoolSize
nThreads, // maximumPoolSize
60L, // keepAliveTime
TimeUnit.SECONDS, // keepAliveTime's unit
new LinkedBlockingQueue<>(), // workQueue
Executors.defaultThreadFactory(), // threadFactory
new ThreadPoolExecutor.CallerRunsPolicy() // handler
);
6.總結
- IO密集型任務: 使用公式 計算執行緒池大小。
- CPU密集型任務: 使用公式 計算執行緒池大小。
- 混合型任務: 綜合IO和CPU的公式進行計算和調整。
-
W
: 平均等待時間C
: 平均計算時間
- 實際應用: 根據QPS或TPS、響應時間、期望的CPU利用率等引數進行計算,並定期監控系統負載進行調整。
合理的執行緒池配置可以顯著提升系統的處理能力和資源利用率,因此根據具體需求和系統指標進行精細配置是至關重要的。