1. 線程池的基本概念
線程池是用來管理線程的工具,它可以有效地控制併發任務的執行。通過線程池,可以:
- 降低創建和銷燬線程的開銷。
- 限制同時運行的線程數,避免過多線程導致系統資源過載。
- 提供線程複用,減少上下文切換的開銷。
2. 線程池的核心參數
- 核心線程數 (
corePoolSize):線程池中始終保持活動的線程數。如果任務隊列中有任務,線程池會首先使用這些核心線程來執行任務。 - 最大線程數 (
maximumPoolSize):線程池中允許的最大線程數。當線程池中的線程數達到核心線程數時,若隊列滿了,線程池將創建新的線程直到最大線程數。 - 空閒線程存活時間 (
keepAliveTime):當線程池中的線程數大於核心線程數時,空閒線程的最大存活時間。 - 任務隊列 (
workQueue):用來保存等待執行任務的隊列。當線程池中所有核心線程都在執行任務時,新任務會被添加到任務隊列中。 - 拒絕策略 (
RejectedExecutionHandler):當線程池無法處理新的任務時的處理策略。
3. 根據任務類型設置線程池參數
CPU 密集型任務的線程池配置
- CPU 密集型任務:這些任務需要大量的計算,不會有明顯的 IO 等待時間。過多的線程會導致線程之間競爭 CPU 資源,增加上下文切換的開銷,從而降低系統性能。
推薦配置:
- 核心線程數:
CPU 核數,讓每個核心都有一個線程。 - 最大線程數:
CPU 核數,不需要超過 CPU 核數。 - 隊列類型:通常選擇
LinkedBlockingQueue(無界隊列)或ArrayBlockingQueue(有界隊列)。 - 任務數較少:可考慮設置較小的隊列長度,避免內存佔用過多。
示例:
ExecutorService executor = new ThreadPoolExecutor(
2, // 核心線程數(例如:2 核 CPU)
2, // 最大線程數(例如:2 核 CPU)
0L, TimeUnit.MILLISECONDS, // 空閒線程存活時間
new LinkedBlockingQueue<>(100) // 隊列大小
);
IO 密集型任務的線程池配置
- IO 密集型任務:這些任務大部分時間處於等待外部資源(如文件 I/O、網絡 I/O 等)期間,因此它們不佔用大量 CPU 資源,可以使用更多的線程來提高吞吐量。
推薦配置:
- 核心線程數:
CPU 核數,通常設置為 CPU 核數。 - 最大線程數:
CPU 核數 / (1 - 阻塞係數),根據任務的阻塞係數估算線程數。例如,阻塞係數為 0.7 時,可以設置最大線程數為2 / (1 - 0.7) = 6。 - 任務隊列:通常使用
LinkedBlockingQueue或ArrayBlockingQueue,根據內存和任務量的實際情況來調整隊列大小。
示例:
int cpuCoreCount = 2;
double blockFactor = 0.7; // 阻塞係數
int maxThreads = (int) Math.ceil(cpuCoreCount / (1 - blockFactor)); // 計算最大線程數
ExecutorService executorService = new ThreadPoolExecutor(
cpuCoreCount, // 核心線程數
maxThreads, // 最大線程數
0L, TimeUnit.MILLISECONDS, // 空閒線程存活時間
new LinkedBlockingQueue<>(100) // 任務隊列
);
4. 線程池的空閒線程存活時間
- 空閒線程存活時間:對於短生命週期的任務,可以將線程的空閒存活時間設置為 0,以便線程池在任務執行完畢後立即銷燬空閒線程。而對於長期運行的服務,可以適當增加線程的空閒存活時間,避免頻繁的線程創建和銷燬。
示例:
new ThreadPoolExecutor(
2, // 核心線程數
6, // 最大線程數
60L, TimeUnit.SECONDS, // 空閒線程存活時間
new LinkedBlockingQueue<>(100) // 任務隊列
);
5. 線程池的拒絕策略
當線程池無法處理新的任務時,會觸發 拒絕策略。常見的拒絕策略有:
- AbortPolicy(默認策略):拋出
RejectedExecutionException異常。 - CallerRunsPolicy:由調用者線程執行任務,避免任務丟失。
- DiscardPolicy:直接丟棄任務。
- DiscardOldestPolicy:丟棄最舊的任務。
示例:
new ThreadPoolExecutor(
2, 6, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100),
new ThreadPoolExecutor.CallerRunsPolicy() // 使用調用者執行策略
);
6. 總結
- 對於 CPU 密集型任務,線程數通常設置為 CPU 核數,最大線程數不超過 CPU 核數,避免過多線程引發上下文切換的性能損失。
- 對於 IO 密集型任務,由於任務在等待 IO 時不會佔用 CPU,可以適當增加線程數。最大線程數的設置可以參考公式:$$ \text{最大線程數} = \frac{\text{CPU 核數}}{1 - \text{阻塞係數}} $$。
- 合理的線程池參數配置能顯著提高系統的性能,避免線程過多帶來的資源浪費和上下文切換開銷。