深入源码分析Java线程池的实现原理
轉(zhuǎn)載自? ?深入源碼分析Java線程池的實(shí)現(xiàn)原理
程序的運(yùn)行,其本質(zhì)上,是對(duì)系統(tǒng)資源(CPU、內(nèi)存、磁盤(pán)、網(wǎng)絡(luò)等等)的使用。如何高效的使用這些資源是我們編程優(yōu)化演進(jìn)的一個(gè)方向。今天說(shuō)的線程池就是一種對(duì)CPU利用的優(yōu)化手段。
網(wǎng)上有不少介紹如何使用線程池的文章,那我想說(shuō)點(diǎn)什么呢?我希望通過(guò)學(xué)習(xí)線程池原理,明白所有池化技術(shù)的基本設(shè)計(jì)思路。遇到其他相似問(wèn)題可以解決。
?
池化技術(shù)
前面提到一個(gè)名詞——池化技術(shù),那么到底什么是池化技術(shù)呢?
池化技術(shù)簡(jiǎn)單點(diǎn)來(lái)說(shuō),就是提前保存大量的資源,以備不時(shí)之需。在機(jī)器資源有限的情況下,使用池化技術(shù)可以大大的提高資源的利用率,提升性能等。
在編程領(lǐng)域,比較典型的池化技術(shù)有:
線程池、連接池、內(nèi)存池、對(duì)象池等。
本文主要來(lái)介紹一下其中比較簡(jiǎn)單的線程池的實(shí)現(xiàn)原理,希望讀者們可以舉一反三,通過(guò)對(duì)線程池的理解,學(xué)習(xí)并掌握所有編程中池化技術(shù)的底層原理。
?
創(chuàng)建一個(gè)線程
在Java的并發(fā)編程中,線程是十分重要的,在Java中,創(chuàng)建一個(gè)線程比較簡(jiǎn)單:
public?class?App?{public?static?void?main(String[]?args)?throws?Exception?{new?Thread(new?Runnable()?{@Overridepublic?void?run()?{System.out.println("線程運(yùn)行中");}}).start();} }我們通過(guò)創(chuàng)建一個(gè)線程對(duì)象,并且實(shí)現(xiàn)Runnable接口就可以實(shí)現(xiàn)一個(gè)簡(jiǎn)單的線程。可以利用上多核CPU。當(dāng)一個(gè)任務(wù)結(jié)束,當(dāng)前線程就接收。
但很多時(shí)候,我們不止會(huì)執(zhí)行一個(gè)任務(wù)。如果每次都是如此的創(chuàng)建線程->執(zhí)行任務(wù)->銷(xiāo)毀線程,會(huì)造成很大的性能開(kāi)銷(xiāo)。
那能否一個(gè)線程創(chuàng)建后,執(zhí)行完一個(gè)任務(wù)后,又去執(zhí)行另一個(gè)任務(wù),而不是銷(xiāo)毀。這就是線程池。
這也就是池化技術(shù)的思想,通過(guò)預(yù)先創(chuàng)建好多個(gè)線程,放在池中,這樣可以在需要使用線程的時(shí)候直接獲取,避免多次重復(fù)創(chuàng)建、銷(xiāo)毀帶來(lái)的開(kāi)銷(xiāo)。
線程池的簡(jiǎn)單使用
以下代碼,是在Java中創(chuàng)建線程池:
import?java.util.concurrent.*;public?class?App?{public?static?void?main(String[]?args)?throws?Exception?{ExecutorService?executorService?=?new?ThreadPoolExecutor(1,?1,60L,?TimeUnit.SECONDS,new?ArrayBlockingQueue<>(10));executorService.execute(new?Runnable()?{@Overridepublic?void?run()?{System.out.println("abcdefg");}});executorService.shutdown();} }Jdk提供給外部的接口也很簡(jiǎn)單。直接調(diào)用ThreadPoolExecutor構(gòu)造一個(gè)就可以了,也可以通過(guò)Executors靜態(tài)工廠構(gòu)建,但一般不建議。
可以看到,開(kāi)發(fā)者想要在代碼中使用線程池還是比較簡(jiǎn)單的,這得益于Java給我們封裝好的一系列API。但是,這些API的背后是什么呢,讓我們來(lái)揭開(kāi)這個(gè)迷霧,看清線程池的本質(zhì)。
?
?
線程池構(gòu)造函數(shù)
通常,一般構(gòu)造函數(shù)會(huì)反映出這個(gè)工具或這個(gè)對(duì)象的數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)。
?
如果把線程池比作一個(gè)公司。公司會(huì)有正式員工處理正常業(yè)務(wù),如果工作量大的話,會(huì)雇傭外包人員來(lái)工作。
閑時(shí)就可以釋放外包人員以減少公司管理開(kāi)銷(xiāo)。一個(gè)公司因?yàn)槌杀娟P(guān)系,雇傭的人員始終是有最大數(shù)。
如果這時(shí)候還有任務(wù)處理不過(guò)來(lái),就走需求池排任務(wù)。
-
acc : 獲取調(diào)用上下文
-
corePoolSize: 核心線程數(shù)量,可以類(lèi)比正式員工數(shù)量,常駐線程數(shù)量。
-
maximumPoolSize: 最大的線程數(shù)量,公司最多雇傭員工數(shù)量。常駐+臨時(shí)線程數(shù)量。
-
workQueue:多余任務(wù)等待隊(duì)列,再多的人都處理不過(guò)來(lái)了,需要等著,在這個(gè)地方等。
-
keepAliveTime:非核心線程空閑時(shí)間,就是外包人員等了多久,如果還沒(méi)有活干,解雇了。
-
threadFactory: 創(chuàng)建線程的工廠,在這個(gè)地方可以統(tǒng)一處理創(chuàng)建的線程的屬性。每個(gè)公司對(duì)員工的要求不一樣,恩,在這里設(shè)置員工的屬性。
-
handler:線程池拒絕策略,什么意思呢?就是當(dāng)任務(wù)實(shí)在是太多,人也不夠,需求池也排滿了,還有任務(wù)咋辦?默認(rèn)是不處理,拋出異常告訴任務(wù)提交者,我這忙不過(guò)來(lái)了。
?
?
添加一個(gè)任務(wù)
接著,我們看一下線程池中比較重要的execute方法,該方法用于向線程池中添加一個(gè)任務(wù)。
?
核心模塊用紅框標(biāo)記了。
-
第一個(gè)紅框:workerCountOf方法根據(jù)ctl的低29位,得到線程池的當(dāng)前線程數(shù),如果線程數(shù)小于corePoolSize,則執(zhí)行addWorker方法創(chuàng)建新的線程執(zhí)行任務(wù);
-
第二個(gè)紅框:判斷線程池是否在運(yùn)行,如果在,任務(wù)隊(duì)列是否允許插入,插入成功再次驗(yàn)證線程池是否運(yùn)行,如果不在運(yùn)行,移除插入的任務(wù),然后拋出拒絕策略。如果在運(yùn)行,沒(méi)有線程了,就啟用一個(gè)線程。
-
第三個(gè)紅框:如果添加非核心線程失敗,就直接拒絕了。
這里邏輯稍微有點(diǎn)復(fù)雜,畫(huà)了個(gè)流程圖僅供參考
?
接下來(lái),我們看看如何添加一個(gè)工作線程的?
?
?
添加worker線程
從方法execute的實(shí)現(xiàn)可以看出:addWorker主要負(fù)責(zé)創(chuàng)建新的線程并執(zhí)行任務(wù),代碼如下(這里代碼有點(diǎn)長(zhǎng),沒(méi)關(guān)系,也是分塊的,總共有5個(gè)關(guān)鍵的代碼塊):
?
-
第一個(gè)紅框:做是否能夠添加工作線程條件過(guò)濾:
-
判斷線程池的狀態(tài),如果線程池的狀態(tài)值大于或等SHUTDOWN,則不處理提交的任務(wù),直接返回;
-
-
第二個(gè)紅框:做自旋,更新創(chuàng)建線程數(shù)量:
-
通過(guò)參數(shù)core判斷當(dāng)前需要?jiǎng)?chuàng)建的線程是否為核心線程,如果core為true,且當(dāng)前線程數(shù)小于corePoolSize,則跳出循環(huán),開(kāi)始創(chuàng)建新的線程
-
有人或許會(huì)疑問(wèn) retry 是什么?這個(gè)是java中的goto語(yǔ)法。只能運(yùn)用在break和continue后面。
接著看后面的代碼:
?
-
第一個(gè)紅框:獲取線程池主鎖。
-
線程池的工作線程通過(guò)Woker類(lèi)實(shí)現(xiàn),通過(guò)ReentrantLock鎖保證線程安全。
-
-
第二個(gè)紅框:添加線程到workers中(線程池中)。
-
第三個(gè)紅框:啟動(dòng)新建的線程。
?
接下來(lái),我們看看workers是什么。
?
一個(gè)hashSet。所以,線程池底層的存儲(chǔ)結(jié)構(gòu)其實(shí)就是一個(gè)HashSet。
?
?
worker線程處理隊(duì)列任務(wù)
?
-
第一個(gè)紅框:是否是第一次執(zhí)行任務(wù),或者從隊(duì)列中可以獲取到任務(wù)。
-
第二個(gè)紅框:獲取到任務(wù)后,執(zhí)行任務(wù)開(kāi)始前操作鉤子。
-
第三個(gè)紅框:執(zhí)行任務(wù)。
-
第四個(gè)紅框:執(zhí)行任務(wù)后鉤子。
這兩個(gè)鉤子(beforeExecute,afterExecute)允許我們自己繼承線程池,做任務(wù)執(zhí)行前后處理。
到這里,源代碼分析到此為止。接下來(lái)做一下簡(jiǎn)單的總結(jié)。
?
?
總結(jié)
所謂線程池本質(zhì)是一個(gè)hashSet。多余的任務(wù)會(huì)放在阻塞隊(duì)列中。
只有當(dāng)阻塞隊(duì)列滿了后,才會(huì)觸發(fā)非核心線程的創(chuàng)建。所以非核心線程只是臨時(shí)過(guò)來(lái)打雜的。直到空閑了,然后自己關(guān)閉了。
線程池提供了兩個(gè)鉤子(beforeExecute,afterExecute)給我們,我們繼承線程池,在執(zhí)行任務(wù)前后做一些事情。
線程池原理關(guān)鍵技術(shù):鎖(lock,cas)、阻塞隊(duì)列、hashSet(資源池)
?
最后希望對(duì)你理解線程池有幫助。最后,留一個(gè)思考題,為什么線程池的底層數(shù)據(jù)接口采用HashSet來(lái)實(shí)現(xiàn)?
總結(jié)
以上是生活随笔為你收集整理的深入源码分析Java线程池的实现原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 全球报纸大索引[2022年更新]
- 下一篇: 你必须掌握的 21 个 Java 核心技