點(diǎn)贊關(guān)注,不再迷路唯咬,你的支持對(duì)我意義重大!
?? Hi畏浆,我是丑丑胆胰。本文 「Java 路線」| 導(dǎo)讀 —— 他山之石,可以攻玉 已收錄刻获,這里有 Android 進(jìn)階成長(zhǎng)路線筆記 & 博客蜀涨,歡迎跟著彭丑丑一起成長(zhǎng)。(聯(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移袍、降低資源消耗: 線程是稀缺資源,如果無(wú)限制 / 重復(fù)創(chuàng)建老充,會(huì)消耗系統(tǒng)資源葡盗;
2、提高響應(yīng)速度: 通過(guò)復(fù)用線程來(lái)執(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ú)法感知任務(wù)執(zhí)行完成;
submit(): 用于需要返回值的任務(wù)次酌,通過(guò)返回值 Future 可以獲得返回值恨课,如果任務(wù)未執(zhí)行完成舆乔,調(diào)用
Futrue#get(...)
會(huì)阻塞當(dāng)前線程。
2.4 關(guān)閉線程池
shutdown() / shutdownNow()
線程中斷協(xié)作機(jī)制
2.5 阿里巴巴編程規(guī)范
根據(jù)《阿里巴巴 Java 開(kāi)發(fā)手冊(cè)》庄呈,線程池不允許使用 Executors 去創(chuàng)建蜕煌,而是通過(guò) 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ì)列同云,使用無(wú)界隊(duì)列會(huì)導(dǎo)致影響線程池的工作機(jī)制,原因:
- 1堵腹、使用無(wú)界隊(duì)列炸站,意味著阻塞隊(duì)列永遠(yuǎn)不會(huì)占滿,maximunPoolSize 和 keepAliveTime 是無(wú)效的疚顷;
- 2旱易、無(wú)界隊(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)建新的線程」來(lái)執(zhí)行任務(wù)(注意登淘,執(zhí)行這一步驟需要獲取全局鎖);
- 2封字、如果運(yùn)行的線程等于或多于 corePoolSize黔州,則將任務(wù)「加入 BlockingQueue」;
- 3阔籽、如果 BlockingQueue 已滿流妻,則「創(chuàng)建新的線程」來(lái)處理任務(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 需要訪問(wèn)的數(shù)據(jù)在虛擬內(nèi)存上時(shí)箕憾,當(dāng)前線程就進(jìn)入了 “頁(yè)缺失” 狀態(tài),需要等待數(shù)據(jù)從磁盤調(diào)度到真實(shí)內(nèi)存才會(huì)喚醒拳昌。為了防止出現(xiàn) “頁(yè)缺失” 時(shí)袭异,CPU 空閑出來(lái),保證任意時(shí)刻 CPU 都不會(huì)空閑炬藤,可以選擇 + 1御铃;
6.2 優(yōu)先級(jí)
需要區(qū)分任務(wù)優(yōu)先級(jí),則使用 PriorityBlockingQueue刻像;
6.3 執(zhí)行耗時(shí)
執(zhí)行時(shí)間不同的任務(wù)可以交給不同規(guī)模的線程池來(lái)處理畅买,或者可以使用優(yōu)先級(jí)隊(duì)列并闲,讓執(zhí)行時(shí)間短的任務(wù)先執(zhí)行细睡;
6.4 建議使用有界隊(duì)列
無(wú)界隊(duì)列沒(méi)有限制隊(duì)列元素個(gè)數(shù),有可能有造成資源耗盡帝火。
7. 總結(jié)
創(chuàng)作不易溜徙,你的「三連」是丑丑最大的動(dòng)力,我們下次見(jiàn)犀填!