線程池
為什么要使用線程池
在實(shí)際使用中联予,線程是很占用系統(tǒng)資源的啼县,如果對(duì)線程管理不善很容易導(dǎo)致系統(tǒng)問(wèn)題。 因此躯泰,在大多數(shù)并發(fā)框架中都會(huì)使用線程池來(lái)管理線程谭羔,使用線程池管理線程主要有如下好處:
(1)降低資源消耗。通過(guò)復(fù)用已存在的線程和降低線程關(guān)閉的次數(shù)來(lái)盡可能降低系統(tǒng)性能損耗
(2)提升系統(tǒng)響應(yīng)速度麦向。通過(guò)復(fù)用線程瘟裸,省去創(chuàng)建線程的過(guò)程,因此整體上提升了系統(tǒng)的響應(yīng)速度
(3)提高線程的可管理性诵竭。線程是稀缺資源话告,如果無(wú)限制的創(chuàng)建兼搏,不僅會(huì)消耗系統(tǒng)資源, 還會(huì)降低系統(tǒng)的穩(wěn)定性沙郭,因此佛呻,需要使用線程池來(lái)管理線程。
線程池的工作原理
當(dāng)一個(gè)并發(fā)任務(wù)提交給線程池病线,線程池分配線程去執(zhí)行任務(wù)的過(guò)程如下:
線程池執(zhí)行所提交的任務(wù)過(guò)程主要有這樣幾個(gè)階段:
(1)先判斷線程池中核心線程池所有的線程是否都在執(zhí)行任務(wù)吓著。 如果不是,則新創(chuàng)建一個(gè)線程執(zhí)行剛提交的任務(wù)送挑,否則绑莺,核心線程池中所有的線程都在執(zhí)行任務(wù),則進(jìn)入(2)
(2)判斷當(dāng)前阻塞隊(duì)列是否已滿惕耕,如果未滿纺裁, 則將提交的任務(wù)放置在阻塞隊(duì)列中;否則司澎,則進(jìn)入(3)
(3)判斷線程池中所有的線程是否都在執(zhí)行任務(wù)欺缘, 如果沒(méi)有,則創(chuàng)建一個(gè)新的線程來(lái)執(zhí)行任務(wù)挤安,否則谚殊,則交給飽和策略進(jìn)行處理
線程池執(zhí)行邏輯
通過(guò)ThreadPoolExecutor創(chuàng)建線程池后,提交任務(wù)后執(zhí)行過(guò)程是怎樣的漱受,下面來(lái)通過(guò)源碼來(lái)看一看络凿。execute()方法源碼如下:
public void execute(Runnable command) {
? ? if (command == null)
? ? ? ? throw new NullPointerException();
? ? int c = ctl.get();
? ? //如果線程池的線程個(gè)數(shù)少于corePoolSize則創(chuàng)建新線程執(zhí)行當(dāng)前任務(wù)
? ? if (workerCountOf(c) < corePoolSize) {
? ? ? ? if (addWorker(command, true))
? ? ? ? ? ? return;
? ? ? ? c = ctl.get();
? ? }
? ? //如果線程個(gè)數(shù)大于corePoolSize或者創(chuàng)建線程失敗,則將任務(wù)存放在阻塞隊(duì)列workQueue中
? ? if (isRunning(c) && workQueue.offer(command)) {
? ? ? ? int recheck = ctl.get();
? ? ? ? if (! isRunning(recheck) && remove(command))
? ? ? ? ? ? reject(command);
? ? ? ? else if (workerCountOf(recheck) == 0)
? ? ? ? ? ? addWorker(null, false);
? ? }
? ? //如果當(dāng)前任務(wù)無(wú)法放進(jìn)阻塞隊(duì)列中昂羡,則創(chuàng)建新的線程來(lái)執(zhí)行任務(wù)
? ? else if (!addWorker(command, false))
? ? ? ? reject(command);
}
execute()執(zhí)行過(guò)程:
execute方法執(zhí)行邏輯有這樣幾種情況:
(1)如果當(dāng)前運(yùn)行的線程少于corePoolSize,則會(huì)創(chuàng)建新的線程來(lái)執(zhí)行新的任務(wù)摔踱,即使線程池中的其他線程是空閑的虐先;
(2)如果運(yùn)行的線程個(gè)數(shù)等于或者大于corePoolSize且小于maximumPoolSize,則會(huì)將提交的任務(wù)存放到阻塞隊(duì)列workQueue中派敷;
(3)如果當(dāng)前workQueue隊(duì)列已滿的話蛹批,則會(huì)創(chuàng)建新的線程來(lái)執(zhí)行任務(wù);
(4)如果線程個(gè)數(shù)已經(jīng)超過(guò)了maximumPoolSize篮愉,則會(huì)使用飽和策略RejectedExecutionHandler來(lái)進(jìn)行處理增量的任務(wù)腐芍。
線程池的關(guān)閉
關(guān)閉線程池,可以通過(guò)shutdown和shutdownNow這兩個(gè)方法试躏。 它們的原理都是遍歷線程池中所有的線程猪勇,然后依次中斷線程。 shutdown和shutdownNow還是有不一樣的地方:
shutdownNow首先將線程池的狀態(tài)設(shè)置為STOP,然后嘗試停止所有的正在執(zhí)行和未執(zhí)行任務(wù)的線程颠蕴,并返回等待執(zhí)行任務(wù)的列表
shutdown只是將線程池的狀態(tài)設(shè)置為SHUTDOWN狀態(tài)泣刹,然后中斷所有沒(méi)有正在執(zhí)行任務(wù)的線程
可以看出shutdown方法會(huì)將正在執(zhí)行的任務(wù)繼續(xù)執(zhí)行完助析,而shutdownNow會(huì)直接中斷正在執(zhí)行的任務(wù)。 調(diào)用了這兩個(gè)方法的任意一個(gè)椅您,isShutdown方法都會(huì)返回true外冀, 當(dāng)所有的線程都關(guān)閉成功,才表示線程池成功關(guān)閉掀泳,這時(shí)調(diào)用isTerminated方法才會(huì)返回true雪隧。
如何合理配置線程池參數(shù)
要想合理的配置線程池,就必須首先分析任務(wù)特性员舵,可以從以下幾個(gè)角度來(lái)進(jìn)行分析:
任務(wù)的性質(zhì):CPU密集型任務(wù)膀跌,IO密集型任務(wù)和混合型任務(wù)。
任務(wù)的優(yōu)先級(jí):高固灵,中和低捅伤。
任務(wù)的執(zhí)行時(shí)間:長(zhǎng),中和短巫玻。
任務(wù)的依賴性:是否依賴其他系統(tǒng)資源丛忆,如數(shù)據(jù)庫(kù)連接。
CPU密集型任務(wù)配置盡可能少的線程數(shù)量仍秤,如配置(N cpu)+1個(gè)線程的線程池熄诡。
IO密集型任務(wù)則由于需要等待IO操作,線程并不是一直在執(zhí)行任務(wù)诗力,則配置盡可能多的線程凰浮,如2 * (N cpu)。
混合型的任務(wù)苇本,如果可以拆分袜茧,則將其拆分成一個(gè)CPU密集型任務(wù)和一個(gè)IO密集型任務(wù),只要這兩個(gè)任務(wù)執(zhí)行的時(shí)間相差不是太大瓣窄,那么分解后執(zhí)行的吞吐率要高于串行執(zhí)行的吞吐率笛厦,如果這兩個(gè)任務(wù)執(zhí)行時(shí)間相差太大,則沒(méi)必要進(jìn)行分解俺夕。
我們可以通過(guò)Runtime.getRuntime().availableProcessors()方法獲得當(dāng)前設(shè)備的CPU個(gè)數(shù)裳凸。
優(yōu)先級(jí)不同的任務(wù)可以使用優(yōu)先級(jí)隊(duì)列PriorityBlockingQueue來(lái)處理。它可以讓優(yōu)先級(jí)高的任務(wù)先得到執(zhí)行劝贸,需要注意的是如果一直有優(yōu)先級(jí)高的任務(wù)提交到隊(duì)列里姨谷,那么優(yōu)先級(jí)低的任務(wù)可能永遠(yuǎn)不能執(zhí)行。
執(zhí)行時(shí)間不同的任務(wù)可以交給不同規(guī)模的線程池來(lái)處理映九,或者也可以使用優(yōu)先級(jí)隊(duì)列梦湘,讓執(zhí)行時(shí)間短的任務(wù)先執(zhí)行。
依賴數(shù)據(jù)庫(kù)連接池的任務(wù),因?yàn)榫€程提交SQL后需要等待數(shù)據(jù)庫(kù)返回結(jié)果践叠,如果等待的時(shí)間越長(zhǎng)CPU空閑時(shí)間就越長(zhǎng)言缤,那么線程數(shù)應(yīng)該設(shè)置越大,這樣才能更好的利用CPU禁灼。
阻塞隊(duì)列最好是使用有界隊(duì)列管挟,如果采用無(wú)界隊(duì)列的話,一旦任務(wù)積壓在阻塞隊(duì)列中的話就會(huì)占用過(guò)多的內(nèi)存資源弄捕,甚至?xí)沟孟到y(tǒng)崩潰僻孝。
ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor繼承了ThreadPoolExecutor類, 因此守谓,整體上功能一致穿铆,線程池主要負(fù)責(zé)創(chuàng)建線程(Worker類), 線程從阻塞隊(duì)列中不斷獲取新的異步任務(wù)斋荞,直到阻塞隊(duì)列中已經(jīng)沒(méi)有了異步任務(wù)為止荞雏。 但是相較于ThreadPoolExecutor來(lái)說(shuō),ScheduledThreadPoolExecutor 具有延時(shí)執(zhí)行任務(wù)和周期性執(zhí)行任務(wù)的特性平酿, ScheduledThreadPoolExecutor重新設(shè)計(jì)了任務(wù)類ScheduleFutureTask,?ScheduleFutureTask重寫了run方法使其具有可延時(shí)執(zhí)行和可周期性執(zhí)行任務(wù)的特性凤优。 另外,阻塞隊(duì)列DelayedWorkQueue是可根據(jù)優(yōu)先級(jí)排序的隊(duì)列蜈彼,采用了堆的底層數(shù)據(jù)結(jié)構(gòu)筑辨, 使得與當(dāng)前時(shí)間相比,將待執(zhí)行時(shí)間越靠近的任務(wù)放置到隊(duì)頭幸逆,以便線程能夠獲取到任務(wù)進(jìn)行執(zhí)行
線程池?zé)o論是ThreadPoolExecutor還是ScheduledThreadPoolExecutor棍辕, 在設(shè)計(jì)時(shí)的三個(gè)關(guān)鍵要素是:任務(wù)、執(zhí)行者以及任務(wù)結(jié)果还绘。 它們的設(shè)計(jì)思想也是完全將這三個(gè)關(guān)鍵要素進(jìn)行了解耦楚昭。
執(zhí)行者
任務(wù)的執(zhí)行機(jī)制,完全交由Worker類蚕甥,也就是進(jìn)一步了封裝了Thread哪替。 向線程池提交任務(wù),無(wú)論為ThreadPoolExecutor的execute方法和submit方法菇怀, 還是ScheduledThreadPoolExecutor的schedule方法,都是先將任務(wù)移入到阻塞隊(duì)列中晌块, 然后通過(guò)addWork方法新建了Work類爱沟,并通過(guò)runWorker方法啟動(dòng)線程,并 不斷的從阻塞對(duì)列中獲取異步任務(wù)執(zhí)行交給Worker執(zhí)行匆背,直至阻塞隊(duì)列中無(wú)法取到任務(wù)為止呼伸。
任務(wù)
在ThreadPoolExecutor和ScheduledThreadPoolExecutor中任務(wù)是指實(shí)現(xiàn)了Runnable接口和Callable接口的實(shí)現(xiàn)類。 ThreadPoolExecutor中會(huì)將任務(wù)轉(zhuǎn)換成FutureTask類, 而在ScheduledThreadPoolExecutor中為了實(shí)現(xiàn)可延時(shí)執(zhí)行任務(wù)和周期性執(zhí)行任務(wù)的特性括享, 任務(wù)會(huì)被轉(zhuǎn)換成ScheduledFutureTask類搂根,該類繼承了FutureTask,并重寫了run方法铃辖。
任務(wù)結(jié)果
在ThreadPoolExecutor中提交任務(wù)后剩愧,獲取任務(wù)結(jié)果可以通過(guò)Future接口的類, 在ThreadPoolExecutor中實(shí)際上為FutureTask類娇斩, 而在ScheduledThreadPoolExecutor中則是ScheduledFutureTask類
線程池的狀態(tài)
線程池的狀態(tài)有:
RUNNING:能接受新提交的任務(wù)仁卷,并且也能夠處理阻塞隊(duì)列中的任務(wù);
SHUTDOWN:不再接受新提交的任務(wù)犬第,但是可以處理存量任務(wù)(即阻塞隊(duì)列中的任務(wù))锦积;
STOP:不再接受新提交的任務(wù),也不處理存量任務(wù)歉嗓;
TIDYING:所有任務(wù)都已終止丰介;
TERMINATED:默認(rèn)是什么也不做的,只是作為一個(gè)標(biāo)識(shí)鉴分。
狀態(tài)轉(zhuǎn)移如下圖所示:
工作線程的生命周期: