
點(diǎn)贊關(guān)注,不再迷路,你的支持對(duì)我意義重大!
?? Hi,我是丑丑。本文 「Java 路線」| 導(dǎo)讀 —— 他山之石,可以攻玉 已收錄,這里有 Android 進(jìn)階成長路線筆記 & 博客,歡迎跟著彭丑丑一起成長。(聯(lián)系方式在 GitHub)
前言
- 線程池 是 Java 并發(fā)編程中非常重要的概念,同時(shí)也是面試重點(diǎn)考察的知識(shí)點(diǎn)之一「敲黑板」;
- 在這篇文章里,我將重點(diǎn)分析 線程池工作機(jī)制 & 注意事項(xiàng)。如果能幫上忙,請(qǐng)務(wù)必點(diǎn)贊加關(guān)注,這真的對(duì)我非常重要。
目錄

1. 前置知識(shí)
這篇文章的內(nèi)容會(huì)涉及以下前置 / 相關(guān)知識(shí),貼心的我都幫你準(zhǔn)備好了,請(qǐng)享用~
阻塞隊(duì)列: 「Java 路線」| 阻塞隊(duì)列
線程協(xié)作機(jī)制:【點(diǎn)贊催更】
2. 線程池概述
2.1 為什么要使用線程池?
1、降低資源消耗: 線程是稀缺資源,如果無限制 / 重復(fù)創(chuàng)建,會(huì)消耗系統(tǒng)資源;
2、提高響應(yīng)速度: 通過復(fù)用線程來執(zhí)行任務(wù),可以縮短創(chuàng)建和銷毀線程的時(shí)間;
3、提高線程的可管理性: 使用線程池可以對(duì)線程進(jìn)行統(tǒng)一分配、調(diào)優(yōu)和監(jiān)控。
2.2 線程池如何實(shí)現(xiàn)線程復(fù)用?
線程執(zhí)行完任務(wù)之后,調(diào)用阻塞隊(duì)列BlockingQueue#take()出隊(duì)操作,當(dāng)阻塞隊(duì)列非空時(shí),則繼續(xù)執(zhí)行任務(wù);當(dāng)阻塞隊(duì)列為空時(shí),則當(dāng)前隊(duì)列阻塞。
2.3 提交任務(wù)
向線程池提交任務(wù)可以使用 execute() & submit(),區(qū)別如下:
execute(): 用于不需要返回值的任務(wù),無法感知任務(wù)執(zhí)行完成;
submit(): 用于需要返回值的任務(wù),通過返回值 Future 可以獲得返回值,如果任務(wù)未執(zhí)行完成,調(diào)用
Futrue#get(...)會(huì)阻塞當(dāng)前線程。
2.4 關(guān)閉線程池
shutdown() / shutdownNow()
線程中斷協(xié)作機(jī)制
2.5 阿里巴巴編程規(guī)范
根據(jù)《阿里巴巴 Java 開發(fā)手冊(cè)》,線程池不允許使用 Executors 去創(chuàng)建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學(xué)更加明確線程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險(xiǎn)。
3. 線程池相關(guān)類
Executor
ExecutorService
AbstractExecutorService
ThreadPoolExecutor
ScheduledExecutorService
ScheduledThreadPoolExecutor
4. 線程池參數(shù)
ThreadPoolExecutor.java
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
...
}
| 參數(shù) | 描述 |
|---|---|
| 1、int corePoolSize | 核心線程數(shù) |
| 2、int maximunPoolSize | 最大線程數(shù) |
| 3、long keepAliveTime | 線程空閑最大存活時(shí)間 |
| 4、BlockingQueue workQueue | 阻塞隊(duì)列 |
| 5、ThreadFactory threadFactory | 線程工廠 |
| 6、RejectedExecutionHandler handler | 拒絕策略 |
4.1 int corePoolSize(核心線程數(shù))
如果當(dāng)前線程數(shù)等于 corePoolSize,繼續(xù)提交任務(wù)將進(jìn)入阻塞隊(duì)列中等待。調(diào)用 prestartAllCoreThreads() 可以提前啟動(dòng)所有核心線程;
4.2 int maximunPoolSize(最大線程數(shù))
如果阻塞隊(duì)列滿,繼續(xù)提交任務(wù)將創(chuàng)建新線程,最多可以存在 maximunPoolSize 個(gè)線程;
4.3 long keepAliveTime(線程空閑最大存活時(shí)間)
在線程池空閑時(shí)線程繼續(xù)存活的時(shí)間。注意:keepAliveTime 只有線程數(shù)大于 corePoolSize 才有效;
4.4 BlockingQueue(阻塞隊(duì)列)
如果當(dāng)前線程數(shù)等于 corePoolSize,繼續(xù)提交任務(wù)將進(jìn)入阻塞隊(duì)列中等待。線程池中的阻塞隊(duì)列應(yīng)盡量使用有界隊(duì)列,使用無界隊(duì)列會(huì)導(dǎo)致影響線程池的工作機(jī)制,原因:
- 1、使用無界隊(duì)列,意味著阻塞隊(duì)列永遠(yuǎn)不會(huì)占滿,maximunPoolSize 和 keepAliveTime 是無效的;
- 2、無界隊(duì)列有可能造成系統(tǒng)資源耗盡,同時(shí)即使使用有界隊(duì)列,也要盡量控制在合理范圍內(nèi)。
4.5 ThreadFactory threadFactory(線程工廠)
用于獲取 Thread 對(duì)象的實(shí)例,Executors 中默認(rèn)的線程工廠的線程命名規(guī)則為:pool-「線程池計(jì)數(shù)」-thread-「線程計(jì)數(shù)」
Executors.java
namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
4.6 RejectedExecutionHandler(拒絕策略 )
如果阻塞隊(duì)列滿,且線程數(shù)到達(dá)最大值 maximunPoolSize,繼續(xù)提交任務(wù)則會(huì)觸發(fā)拒絕策略 RejectedExecutionHandler#rejectedExecution(...):
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
線程池提供了以下 4 種拒絕策略:
| 拒絕策略 | 描述 |
|---|---|
| AbortPolicy | 拋出 RejectedExecutionException 異常(默認(rèn)) |
| CallerRunsPolicy | 直接在調(diào)用線程執(zhí)行 |
| DiscardOldestPolicy | 丟棄阻塞隊(duì)列中隊(duì)首的任務(wù) |
| DiscardPolicy | 直接丟棄當(dāng)前任務(wù) |
5. 線程池工作機(jī)制
線程池的工作機(jī)制指的是向線程池提交任務(wù)時(shí),線程池內(nèi)部對(duì)任務(wù)的調(diào)度流程。這部分內(nèi)容是線程池最核心的內(nèi)容,也是面試重點(diǎn)。
- 1、如果當(dāng)前運(yùn)行的線程少于 corePoolSize,則「創(chuàng)建新的線程」來執(zhí)行任務(wù)(注意,執(zhí)行這一步驟需要獲取全局鎖);
- 2、如果運(yùn)行的線程等于或多于 corePoolSize,則將任務(wù)「加入 BlockingQueue」;
- 3、如果 BlockingQueue 已滿,則「創(chuàng)建新的線程」來處理任務(wù);
- 4、如果創(chuàng)建新線程將使當(dāng)前運(yùn)行的線程超出 maximumPoolSize,將「觸發(fā)拒絕策略」并調(diào)用RejectedExecutionHandler#rejectedExecution()方法。

為什么先將任務(wù)加入阻塞隊(duì)列,而不是線程池滿了再加入阻塞隊(duì)列?
Editting...
6. 如何合理配置線程池?
線程池的配置需要根據(jù)「任務(wù)特性」選擇不同的任務(wù)配置,因地制宜。主要從以下角度分析:
6.1 性質(zhì)
任務(wù)的性質(zhì)分為:CPU 密集型、IO 密集型和混合型。
對(duì)于 CPU 密集型任務(wù),CPU 負(fù)載已經(jīng)非常高了,應(yīng)配置盡可能小的線程,經(jīng)驗(yàn)值為 Ncpu + 1(調(diào)用 Runtime.getRuntime().availableProcessors() 獲得可用的核心數(shù)。);
對(duì)于 IO 密集型任務(wù)(如 磁盤 / 網(wǎng)絡(luò) IO),磁盤或網(wǎng)絡(luò)的讀取速度是遠(yuǎn)遠(yuǎn)小于 CPU 執(zhí)行速度的,為了避免 CPU 出現(xiàn)空閑,應(yīng)配置較多的線程,經(jīng)驗(yàn)值為 Ncpu * 2;
對(duì)于混合型任務(wù),則將其拆分為一個(gè) CPU 密集型任務(wù)和 IO 密集型任務(wù),分別到上述兩種線程池執(zhí)行。需要注意的是,如果兩種拆分的兩個(gè)任務(wù)執(zhí)行時(shí)間相差太大,則應(yīng)該視為一種非混合型任務(wù)。
為什么 CPU 密集型線程池經(jīng)驗(yàn)值為 Ncpu + 1?
在操作系統(tǒng)中,會(huì)將磁盤的一部分空間劃分為虛擬內(nèi)存(讀寫速度慢),當(dāng) CPU 需要訪問的數(shù)據(jù)在虛擬內(nèi)存上時(shí),當(dāng)前線程就進(jìn)入了 “頁缺失” 狀態(tài),需要等待數(shù)據(jù)從磁盤調(diào)度到真實(shí)內(nèi)存才會(huì)喚醒。為了防止出現(xiàn) “頁缺失” 時(shí),CPU 空閑出來,保證任意時(shí)刻 CPU 都不會(huì)空閑,可以選擇 + 1;
6.2 優(yōu)先級(jí)
需要區(qū)分任務(wù)優(yōu)先級(jí),則使用 PriorityBlockingQueue;
6.3 執(zhí)行耗時(shí)
執(zhí)行時(shí)間不同的任務(wù)可以交給不同規(guī)模的線程池來處理,或者可以使用優(yōu)先級(jí)隊(duì)列,讓執(zhí)行時(shí)間短的任務(wù)先執(zhí)行;
6.4 建議使用有界隊(duì)列
無界隊(duì)列沒有限制隊(duì)列元素個(gè)數(shù),有可能有造成資源耗盡。
7. 總結(jié)
創(chuàng)作不易,你的「三連」是丑丑最大的動(dòng)力,我們下次見!
