深入分析java線程池的實(shí)現(xiàn)原理

簡(jiǎn)書 占小狼 轉(zhuǎn)載請(qǐng)注明原創(chuàng)出處梅惯,謝謝簸搞!

2019/12/06 于北新涇
時(shí)間過得真快!又兩年多過去了

2017/04/23 于復(fù)興中路裸心社
回頭看看之前寫的這篇文章塞栅,印象中讀源碼的興趣源頭似乎來自于Java線程池者铜,當(dāng)山頭被一座一座攻克時(shí),你會(huì)發(fā)現(xiàn)掉到一個(gè)大坑中放椰,因?yàn)椴欢念I(lǐng)域的實(shí)在太多作烟。

前言

線程是稀缺資源,如果被無限制的創(chuàng)建砾医,不僅會(huì)消耗系統(tǒng)資源拿撩,還會(huì)降低系統(tǒng)的穩(wěn)定性,合理的使用線程池對(duì)線程進(jìn)行統(tǒng)一分配如蚜、調(diào)優(yōu)和監(jiān)控压恒,有以下好處:
1、降低資源消耗错邦;
2探赫、提高響應(yīng)速度;
3撬呢、提高線程的可管理性伦吠。

Java1.5中引入的Executor框架把任務(wù)的提交和執(zhí)行進(jìn)行解耦,只需要定義好任務(wù)倾芝,然后提交給線程池讨勤,而不用關(guān)心該任務(wù)是如何執(zhí)行箭跳、被哪個(gè)線程執(zhí)行晨另,以及什么時(shí)候執(zhí)行。

demo

1谱姓、Executors.newFixedThreadPool(10)初始化一個(gè)包含10個(gè)線程的線程池executor借尿;
2、通過executor.execute方法提交20個(gè)任務(wù),每個(gè)任務(wù)打印當(dāng)前的線程名路翻;
3狈癞、負(fù)責(zé)執(zhí)行任務(wù)的線程的生命周期都由Executor框架進(jìn)行管理;

ThreadPoolExecutor

Executors是java線程池的工廠類茂契,通過它可以快速初始化一個(gè)符合業(yè)務(wù)需求的線程池蝶桶,如Executors.newFixedThreadPool方法可以生成一個(gè)擁有固定線程數(shù)的線程池。

其本質(zhì)是通過不同的參數(shù)初始化一個(gè)ThreadPoolExecutor對(duì)象掉冶,具體參數(shù)描述如下:

corePoolSize

線程池中的核心線程數(shù)真竖,當(dāng)提交一個(gè)任務(wù)時(shí),線程池創(chuàng)建一個(gè)新線程執(zhí)行任務(wù)厌小,直到當(dāng)前線程數(shù)等于corePoolSize恢共;如果當(dāng)前線程數(shù)為corePoolSize,繼續(xù)提交的任務(wù)被保存到阻塞隊(duì)列中璧亚,等待被執(zhí)行讨韭;如果執(zhí)行了線程池的prestartAllCoreThreads()方法,線程池會(huì)提前創(chuàng)建并啟動(dòng)所有核心線程癣蟋。

maximumPoolSize

線程池中允許的最大線程數(shù)透硝。如果當(dāng)前阻塞隊(duì)列滿了,且繼續(xù)提交任務(wù)梢薪,則創(chuàng)建新的線程執(zhí)行任務(wù)蹬铺,前提是當(dāng)前線程數(shù)小于maximumPoolSize;

keepAliveTime

線程空閑時(shí)的存活時(shí)間秉撇,即當(dāng)線程沒有任務(wù)執(zhí)行時(shí)甜攀,繼續(xù)存活的時(shí)間;默認(rèn)情況下琐馆,該參數(shù)只在線程數(shù)大于corePoolSize時(shí)才有用规阀;

unit

keepAliveTime的單位;

workQueue

用來保存等待被執(zhí)行的任務(wù)的阻塞隊(duì)列瘦麸,且任務(wù)必須實(shí)現(xiàn)Runable接口谁撼,在JDK中提供了如下阻塞隊(duì)列:
1、ArrayBlockingQueue:基于數(shù)組結(jié)構(gòu)的有界阻塞隊(duì)列滋饲,按FIFO排序任務(wù)厉碟;
2、LinkedBlockingQuene:基于鏈表結(jié)構(gòu)的阻塞隊(duì)列屠缭,按FIFO排序任務(wù)箍鼓,吞吐量通常要高于ArrayBlockingQuene;
3呵曹、SynchronousQuene:一個(gè)不存儲(chǔ)元素的阻塞隊(duì)列款咖,每個(gè)插入操作必須等到另一個(gè)線程調(diào)用移除操作何暮,否則插入操作一直處于阻塞狀態(tài),吞吐量通常要高于LinkedBlockingQuene铐殃;
4海洼、priorityBlockingQuene:具有優(yōu)先級(jí)的無界阻塞隊(duì)列;

threadFactory

創(chuàng)建線程的工廠富腊,通過自定義的線程工廠可以給每個(gè)新建的線程設(shè)置一個(gè)具有識(shí)別度的線程名坏逢。

handler

線程池的飽和策略,當(dāng)阻塞隊(duì)列滿了赘被,且沒有空閑的工作線程词疼,如果繼續(xù)提交任務(wù),必須采取一種策略處理該任務(wù)帘腹,線程池提供了4種策略:
1贰盗、AbortPolicy:直接拋出異常,默認(rèn)策略阳欲;
2舵盈、CallerRunsPolicy:用調(diào)用者所在的線程來執(zhí)行任務(wù);
3球化、DiscardOldestPolicy:丟棄阻塞隊(duì)列中靠最前的任務(wù)秽晚,并執(zhí)行當(dāng)前任務(wù);
4筒愚、DiscardPolicy:直接丟棄任務(wù)赴蝇;
當(dāng)然也可以根據(jù)應(yīng)用場(chǎng)景實(shí)現(xiàn)RejectedExecutionHandler接口,自定義飽和策略巢掺,如記錄日志或持久化存儲(chǔ)不能處理的任務(wù)句伶。

Exectors

Exectors工廠類提供了線程池的初始化接口,主要有如下幾種:

newFixedThreadPool

初始化一個(gè)指定線程數(shù)的線程池陆淀,其中corePoolSize == maximumPoolSize考余,使用LinkedBlockingQuene作為阻塞隊(duì)列,不過當(dāng)線程池沒有可執(zhí)行任務(wù)時(shí)轧苫,也不會(huì)釋放線程楚堤。

newCachedThreadPool

1、初始化一個(gè)可以緩存線程的線程池含懊,默認(rèn)緩存60s身冬,線程池的線程數(shù)可達(dá)到Integer.MAX_VALUE,即2147483647岔乔,內(nèi)部使用SynchronousQueue作為阻塞隊(duì)列酥筝;
2、和newFixedThreadPool創(chuàng)建的線程池不同重罪,newCachedThreadPool在沒有任務(wù)執(zhí)行時(shí)樱哼,當(dāng)線程的空閑時(shí)間超過keepAliveTime,會(huì)自動(dòng)釋放線程資源剿配,當(dāng)提交新任務(wù)時(shí)搅幅,如果沒有空閑線程,則創(chuàng)建新線程執(zhí)行任務(wù)呼胚,會(huì)導(dǎo)致一定的系統(tǒng)開銷茄唐;

所以,使用該線程池時(shí)蝇更,一定要注意控制并發(fā)的任務(wù)數(shù)沪编,否則創(chuàng)建大量的線程可能導(dǎo)致嚴(yán)重的性能問題。

newSingleThreadExecutor

初始化的線程池中只有一個(gè)線程年扩,如果該線程異常結(jié)束蚁廓,會(huì)重新創(chuàng)建一個(gè)新的線程繼續(xù)執(zhí)行任務(wù),唯一的線程可以保證所提交任務(wù)的順序執(zhí)行厨幻,內(nèi)部使用LinkedBlockingQueue作為阻塞隊(duì)列相嵌。

newScheduledThreadPool

初始化的線程池可以在指定的時(shí)間內(nèi)周期性的執(zhí)行所提交的任務(wù),在實(shí)際的業(yè)務(wù)場(chǎng)景中可以使用該線程池定期的同步數(shù)據(jù)况脆。

實(shí)現(xiàn)原理

除了newScheduledThreadPool的內(nèi)部實(shí)現(xiàn)特殊一點(diǎn)之外饭宾,其它幾個(gè)線程池都是基于ThreadPoolExecutor類實(shí)現(xiàn)的。

線程池內(nèi)部狀態(tài)

其中AtomicInteger變量ctl的功能非常強(qiáng)大:利用低29位表示線程池中線程數(shù)格了,通過高3位表示線程池的運(yùn)行狀態(tài):
1看铆、RUNNING:-1 << COUNT_BITS,即高3位為111盛末,該狀態(tài)的線程池會(huì)接收新任務(wù)弹惦,并處理阻塞隊(duì)列中的任務(wù);
2悄但、SHUTDOWN: 0 << COUNT_BITS肤频,即高3位為000,該狀態(tài)的線程池不會(huì)接收新任務(wù)算墨,但會(huì)處理阻塞隊(duì)列中的任務(wù)宵荒;
3、STOP : 1 << COUNT_BITS净嘀,即高3位為001报咳,該狀態(tài)的線程不會(huì)接收新任務(wù),也不會(huì)處理阻塞隊(duì)列中的任務(wù)挖藏,而且會(huì)中斷正在運(yùn)行的任務(wù)暑刃;
4、TIDYING : 2 << COUNT_BITS膜眠,即高3位為010岩臣;
5溜嗜、TERMINATED: 3 << COUNT_BITS,即高3位為011架谎;

任務(wù)提交

線程池框架提供了兩種方式提交任務(wù)炸宵,根據(jù)不同的業(yè)務(wù)需求選擇不同的方式。

Executor.execute()

通過Executor.execute()方法提交的任務(wù)谷扣,必須實(shí)現(xiàn)Runnable接口土全,該方式提交的任務(wù)不能獲取返回值,因此無法判斷任務(wù)是否執(zhí)行成功会涎。

ExecutorService.submit()

通過ExecutorService.submit()方法提交的任務(wù)裹匙,可以獲取任務(wù)執(zhí)行完的返回值。

任務(wù)執(zhí)行

當(dāng)向線程池中提交一個(gè)任務(wù)末秃,線程池會(huì)如何處理該任務(wù)概页?

execute實(shí)現(xiàn)

具體的執(zhí)行流程如下:
1、workerCountOf方法根據(jù)ctl的低29位练慕,得到線程池的當(dāng)前線程數(shù)绰沥,如果線程數(shù)小于corePoolSize,則執(zhí)行addWorker方法創(chuàng)建新的線程執(zhí)行任務(wù)贺待;否則執(zhí)行步驟(2)徽曲;
2、如果線程池處于RUNNING狀態(tài)麸塞,且把提交的任務(wù)成功放入阻塞隊(duì)列中秃臣,則執(zhí)行步驟(3),否則執(zhí)行步驟(4)哪工;
3奥此、再次檢查線程池的狀態(tài),如果線程池沒有RUNNING雁比,且成功從阻塞隊(duì)列中刪除任務(wù)稚虎,則執(zhí)行reject方法處理任務(wù);
4偎捎、執(zhí)行addWorker方法創(chuàng)建新的線程執(zhí)行任務(wù)蠢终,如果addWoker執(zhí)行失敗,則執(zhí)行reject方法處理任務(wù)茴她;

addWorker實(shí)現(xiàn)

從方法execute的實(shí)現(xiàn)可以看出:addWorker主要負(fù)責(zé)創(chuàng)建新的線程并執(zhí)行任務(wù)寻拂,代碼實(shí)現(xiàn)如下:

這只是addWoker方法實(shí)現(xiàn)的前半部分:
1、判斷線程池的狀態(tài)丈牢,如果線程池的狀態(tài)值大于或等SHUTDOWN祭钉,則不處理提交的任務(wù),直接返回己沛;
2慌核、通過參數(shù)core判斷當(dāng)前需要?jiǎng)?chuàng)建的線程是否為核心線程距境,如果core為true,且當(dāng)前線程數(shù)小于corePoolSize垮卓,則跳出循環(huán)垫桂,開始創(chuàng)建新的線程,具體實(shí)現(xiàn)如下:

線程池的工作線程通過Woker類實(shí)現(xiàn)扒接,在ReentrantLock鎖的保證下,把Woker實(shí)例插入到HashSet后们衙,并啟動(dòng)Woker中的線程钾怔,其中Worker類設(shè)計(jì)如下:
1、繼承了AQS類蒙挑,可以方便的實(shí)現(xiàn)工作線程的中止操作宗侦;
2、實(shí)現(xiàn)了Runnable接口忆蚀,可以將自身作為一個(gè)任務(wù)在工作線程中執(zhí)行矾利;
3、當(dāng)前提交的任務(wù)firstTask作為參數(shù)傳入Worker的構(gòu)造方法馋袜;


從Woker類的構(gòu)造方法實(shí)現(xiàn)可以發(fā)現(xiàn):線程工廠在創(chuàng)建線程thread時(shí)男旗,將Woker實(shí)例本身this作為參數(shù)傳入,當(dāng)執(zhí)行start方法啟動(dòng)線程thread時(shí)欣鳖,本質(zhì)是執(zhí)行了Worker的runWorker方法察皇。

runWorker實(shí)現(xiàn)

runWorker方法是線程池的核心:
1、線程啟動(dòng)之后泽台,通過unlock方法釋放鎖什荣,設(shè)置AQS的state為0,表示運(yùn)行中斷怀酷;
2稻爬、獲取第一個(gè)任務(wù)firstTask,執(zhí)行任務(wù)的run方法蜕依,不過在執(zhí)行任務(wù)之前桅锄,會(huì)進(jìn)行加鎖操作,任務(wù)執(zhí)行完會(huì)釋放鎖样眠;
3竞滓、在執(zhí)行任務(wù)的前后,可以根據(jù)業(yè)務(wù)場(chǎng)景自定義beforeExecute和afterExecute方法吹缔;
4商佑、firstTask執(zhí)行完成之后,通過getTask方法從阻塞隊(duì)列中獲取等待的任務(wù)厢塘,如果隊(duì)列中沒有任務(wù)茶没,getTask方法會(huì)被阻塞并掛起肌幽,不會(huì)占用cpu資源;

getTask實(shí)現(xiàn)

整個(gè)getTask操作在自旋下完成:
1抓半、workQueue.take:如果阻塞隊(duì)列為空喂急,當(dāng)前線程會(huì)被掛起等待;當(dāng)隊(duì)列中有任務(wù)加入時(shí)廊移,線程被喚醒,take方法返回任務(wù)探入,并執(zhí)行蜂嗽;
2完沪、workQueue.poll:如果在keepAliveTime時(shí)間內(nèi),阻塞隊(duì)列還是沒有任務(wù)啊送,則返回null;

所以莱预,線程池中實(shí)現(xiàn)的線程可以一直執(zhí)行由用戶提交的任務(wù)傻谁。

Future和Callable實(shí)現(xiàn)

通過ExecutorService.submit()方法提交的任務(wù)手素,可以獲取任務(wù)執(zhí)行完的返回值言沐。

在實(shí)際業(yè)務(wù)場(chǎng)景中鲸阻,F(xiàn)uture和Callable基本是成對(duì)出現(xiàn)的跋涣,Callable負(fù)責(zé)產(chǎn)生結(jié)果,F(xiàn)uture負(fù)責(zé)獲取結(jié)果鸟悴。
1陈辱、Callable接口類似于Runnable,只是Runnable沒有返回值细诸。
2沛贪、Callable任務(wù)除了返回正常結(jié)果之外,如果發(fā)生異常震贵,該異常也會(huì)被返回利赋,即Future可以拿到異步執(zhí)行任務(wù)各種結(jié)果;
3猩系、Future.get方法會(huì)導(dǎo)致主線程阻塞媚送,直到Callable任務(wù)執(zhí)行完成;

submit實(shí)現(xiàn)

通過submit方法提交的Callable任務(wù)會(huì)被封裝成了一個(gè)FutureTask對(duì)象寇甸。

FutureTask

1塘偎、FutureTask在不同階段擁有不同的狀態(tài)state,初始化為NEW拿霉;
2吟秩、FutureTask類實(shí)現(xiàn)了Runnable接口,這樣就可以通過Executor.execute方法提交FutureTask到線程池中等待被執(zhí)行绽淘,最終執(zhí)行的是FutureTask的run方法涵防;

FutureTask.get實(shí)現(xiàn)

內(nèi)部通過awaitDone方法對(duì)主線程進(jìn)行阻塞,具體實(shí)現(xiàn)如下:

1收恢、如果主線程被中斷武学,則拋出中斷異常;
2伦意、判斷FutureTask當(dāng)前的state火窒,如果大于COMPLETING,說明任務(wù)已經(jīng)執(zhí)行完成驮肉,則直接返回熏矿;
3、如果當(dāng)前state等于COMPLETING,說明任務(wù)已經(jīng)執(zhí)行完票编,這時(shí)主線程只需通過yield方法讓出cpu資源褪储,等待state變成NORMAL;
4慧域、通過WaitNode類封裝當(dāng)前線程鲤竹,并通過UNSAFE添加到waiters鏈表;
5昔榴、最終通過LockSupport的park或parkNanos掛起線程辛藻;

FutureTask.run實(shí)現(xiàn)

FutureTask.run方法是在線程池中被執(zhí)行的,而非主線程
1互订、通過執(zhí)行Callable任務(wù)的call方法吱肌;
2、如果call執(zhí)行成功仰禽,則通過set方法保存結(jié)果氮墨;
3、如果call執(zhí)行有異常吐葵,則通過setException保存異常规揪;

set
setException

set和setException方法中,都會(huì)通過UnSAFE修改FutureTask的狀態(tài)折联,并執(zhí)行finishCompletion方法通知主線程任務(wù)已經(jīng)執(zhí)行完成粒褒;

finishCompletion

1识颊、執(zhí)行FutureTask類的get方法時(shí)诚镰,會(huì)把主線程封裝成WaitNode節(jié)點(diǎn)并保存在waiters鏈表中;
2祥款、FutureTask任務(wù)執(zhí)行完成后清笨,通過UNSAFE設(shè)置waiters的值,并通過LockSupport類unpark方法喚醒主線程刃跛;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末抠艾,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子桨昙,更是在濱河造成了極大的恐慌检号,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛙酪,死亡現(xiàn)場(chǎng)離奇詭異齐苛,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)桂塞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門凹蜂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事玛痊√保” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵擂煞,是天一觀的道長(zhǎng)混弥。 經(jīng)常有香客問我,道長(zhǎng)对省,這世上最難降的妖魔是什么剑逃? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮官辽,結(jié)果婚禮上蛹磺,老公的妹妹穿的比我還像新娘。我一直安慰自己同仆,他們只是感情好萤捆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著俗批,像睡著了一般俗或。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上岁忘,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天辛慰,我揣著相機(jī)與錄音,去河邊找鬼干像。 笑死帅腌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的麻汰。 我是一名探鬼主播速客,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼五鲫!你這毒婦竟也來了溺职?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤位喂,失蹤者是張志新(化名)和其女友劉穎浪耘,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體塑崖,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡七冲,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了弃舒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片癞埠。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡状原,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出苗踪,到底是詐尸還是另有隱情颠区,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布通铲,位于F島的核電站毕莱,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏颅夺。R本人自食惡果不足惜朋截,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吧黄。 院中可真熱鬧部服,春花似錦、人聲如沸拗慨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赵抢。三九已至剧蹂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間烦却,已是汗流浹背宠叼。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留其爵,地道東北人冒冬。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像醋闭,于是被迫代替她去往敵國(guó)和親窄驹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子朝卒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

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