「Java 路線」| 線程池

點(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)享用~


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)犀填!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蠢壹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子九巡,更是在濱河造成了極大的恐慌图贸,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冕广,死亡現(xiàn)場(chǎng)離奇詭異疏日,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)撒汉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門沟优,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人睬辐,你說(shuō)我怎么就攤上這事挠阁。” “怎么了溯饵?”我有些...
    開(kāi)封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵侵俗,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我丰刊,道長(zhǎng)隘谣,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任藻三,我火速辦了婚禮洪橘,結(jié)果婚禮上跪者,老公的妹妹穿的比我還像新娘。我一直安慰自己熄求,他們只是感情好渣玲,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著弟晚,像睡著了一般忘衍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上卿城,一...
    開(kāi)封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天枚钓,我揣著相機(jī)與錄音,去河邊找鬼瑟押。 笑死搀捷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的多望。 我是一名探鬼主播嫩舟,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼怀偷!你這毒婦竟也來(lái)了家厌?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤椎工,失蹤者是張志新(化名)和其女友劉穎饭于,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體维蒙,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掰吕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了木西。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片畴栖。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖八千,靈堂內(nèi)的尸體忽然破棺而出吗讶,到底是詐尸還是另有隱情,我是刑警寧澤恋捆,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布照皆,位于F島的核電站,受9級(jí)特大地震影響沸停,放射性物質(zhì)發(fā)生泄漏膜毁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瘟滨。 院中可真熱鬧候醒,春花似錦、人聲如沸杂瘸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)败玉。三九已至敌土,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間运翼,已是汗流浹背返干。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留血淌,地道東北人矩欠。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像六剥,于是被迫代替她去往敵國(guó)和親晚顷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容