在terminated我們的開(kāi)發(fā)中“池”的概念并不罕見(jiàn)terminated,有數(shù)據(jù)庫(kù)連接池、線程池、對(duì)象池、常量池等等。下面我們主要針對(duì)線程池來(lái)一步一步揭開(kāi)線程池的面紗。
使用線程池的好處
1、降低資源消耗
可以重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷(xiāo)毀造成的消耗。
2、提高響應(yīng)速度
當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行。
3、提高線程的可管理性
線程是稀缺資源,如果無(wú)限制地創(chuàng)建,不僅會(huì)消耗系統(tǒng)資源,還會(huì)降低系統(tǒng)的穩(wěn)定性,使用線程池可以進(jìn)行統(tǒng)一分配、調(diào)優(yōu)和監(jiān)控
線程池的工作原理
首先我們看下當(dāng)一個(gè)新的任務(wù)提交到線程池之后,線程池是如何處理的
1、線程池判斷核心線程池里的線程是否都在執(zhí)行任務(wù)。如果不是,則創(chuàng)建一個(gè)新的工作線程來(lái)執(zhí)行任務(wù)。如果核心線程池里的線程都在執(zhí)行任務(wù),則執(zhí)行第二步。
2、線程池判斷工作隊(duì)列是否已經(jīng)滿。如果工作隊(duì)列沒(méi)有滿,則將新提交的任務(wù)存儲(chǔ)在這個(gè)工作隊(duì)列里進(jìn)行等待。如果工作隊(duì)列滿了,則執(zhí)行第三步
3、線程池判斷線程池的線程是否都處于工作狀態(tài)。如果沒(méi)有,則創(chuàng)建一個(gè)新的工作線程來(lái)執(zhí)行任務(wù)。如果已經(jīng)滿了,則交給飽和策略來(lái)處理這個(gè)任務(wù)
線程池飽和策略
這里提到了線程池的飽和策略,那我們就簡(jiǎn)單介紹下有哪些飽和策略terminated:
AbortPolicy
為Java線程池默認(rèn)的阻塞策略,不執(zhí)行此任務(wù),而且直接拋出一個(gè)運(yùn)行時(shí)異常,切記ThreadPoolexecutor.execute需要try catch,否則程序會(huì)直接退出。
DiscardPolicy
直接拋棄,任務(wù)不執(zhí)行,空方法
DiscardOldestPolicy
從隊(duì)列里面拋棄head的一個(gè)任務(wù),并再次execute 此task。
CallerRunsPolicy
在調(diào)用execute的線程里面執(zhí)行此command,會(huì)阻塞入口
用戶自定義拒絕策略(最常用)
實(shí)現(xiàn)RejectedExecutionHandler,并自己定義策略模式
下我們以ThreadPoolexecutor為例展示下線程池的工作流程圖
1、如果當(dāng)前運(yùn)行的線程少于corePoolSize,則創(chuàng)建新線程來(lái)執(zhí)行任務(wù)(注意,執(zhí)行這一步驟需要獲取全局鎖)。
2、如果運(yùn)行的線程等于或多于corePoolSize,則將任務(wù)加入BlockingQueue。
3、如果無(wú)法將任務(wù)加入BlockingQueue(隊(duì)列已滿),則在非corePool中創(chuàng)建新的線程來(lái)處理任務(wù)(注意,執(zhí)行這一步驟需要獲取全局鎖)。
4、如果創(chuàng)建新線程將使當(dāng)前運(yùn)行的線程超出maximumPoolSize,任務(wù)將被拒絕,并調(diào)用RejectedExecutionHandler.rejectedExecution()方法。
ThreadPoolExecutor采取上述步驟的總體設(shè)計(jì)思路,是為了在執(zhí)行execute()方法時(shí),盡可能地避免獲取全局鎖(那將會(huì)是一個(gè)嚴(yán)重的可伸縮瓶頸)。在ThreadPoolExecutor完成預(yù)熱之后(當(dāng)前運(yùn)行的線程數(shù)大于等于corePoolSize),幾乎所有的execute()方法調(diào)用都是執(zhí)行步驟2,而步驟2不需要獲取全局鎖。
關(guān)鍵方法源碼分析
我們看看核心方法添加到線程池方法execute的源碼如下:
下面我們繼續(xù)看看addWorker是如何實(shí)現(xiàn)的:
addWorker之后是runWorker,第一次啟動(dòng)會(huì)執(zhí)行初始化傳進(jìn)來(lái)的任務(wù)firstTask;然后會(huì)從workQueue中取任務(wù)執(zhí)行,如果隊(duì)列為空則等待keepaliveTime這么長(zhǎng)時(shí)間
我們看下getTask是如何執(zhí)行的
下面我們看下processWorkerExit是如何工作的
tryTerminate
processWorkerExit方法中會(huì)嘗試調(diào)用tryTerminate來(lái)終止線程池。這個(gè)方法在任何可能導(dǎo)致線程池終止的動(dòng)作后執(zhí)行:比如減少wokerCount或SHUTDOWN狀態(tài)下從隊(duì)列中移除任務(wù)。
shutdown這個(gè)方法會(huì)將runState置為SHUTDOWN,會(huì)終止所有空閑的線程。shutdownNow方法將runState置為STOP。和shutdown方法的區(qū)別,這個(gè)方法會(huì)終止所有的線程。主要區(qū)別在于shutdown調(diào)用的是interruptIdleWorkers這個(gè)方法,而shutdownNow實(shí)際調(diào)用的是Worker類的interruptIfStarted方法:
terminated他們的實(shí)現(xiàn)如下:
線程池的使用 線程池的創(chuàng)建
我們可以通過(guò)ThreadPoolExecutor來(lái)創(chuàng)建一個(gè)線程池
向線程池提交任務(wù)
可以使用兩個(gè)方法向線程池提交任務(wù),分別為execute()和submit()方法。execute()方法用于提交不需要返回值的任務(wù),所以無(wú)法判斷任務(wù)是否被線程池執(zhí)行成功。通過(guò)以下代碼可知execute()方法輸入的任務(wù)是一個(gè)Runnable類的實(shí)例。
submit()方法用于提交需要返回值的任務(wù)。線程池會(huì)返回一個(gè)future類型的對(duì)象,通過(guò)這個(gè)future對(duì)象可以判斷任務(wù)是否執(zhí)行成功,并且可以通過(guò)future的get()方法來(lái)獲取返回值,get()方法會(huì)阻塞當(dāng)前線程直到任務(wù)完成,而使用get(long timeout,TimeUnit unit)方法則會(huì)阻塞當(dāng)前線程一段時(shí)間后立即返回,這時(shí)候有可能任務(wù)沒(méi)有執(zhí)行完。
關(guān)閉線程池
可以通過(guò)調(diào)用線程池的shutdown或shutdownNow方法來(lái)關(guān)閉線程池。它們的原理是遍歷線程池中的工作線程,然后逐個(gè)調(diào)用線程的interrupt方法來(lái)中斷線程,所以無(wú)法響應(yīng)中斷的任務(wù)可能永遠(yuǎn)無(wú)法終止。但是它們存在一定的區(qū)別,shutdownNow首先將線程池的狀態(tài)設(shè)置成STOP,然后嘗試停止所有的正在執(zhí)行或暫停任務(wù)的線程,并返回等待執(zhí)行任務(wù)的列表,而shutdown只是將線程池的狀態(tài)設(shè)置成SHUTDOWN狀態(tài),然后中斷所有沒(méi)有正在執(zhí)行任務(wù)的線程。
只要調(diào)用了這兩個(gè)關(guān)閉方法中的任意一個(gè),isShutdown方法就會(huì)返回true。當(dāng)所有的任務(wù)都已關(guān)閉后,才表示線程池關(guān)閉成功,這時(shí)調(diào)用isTerminaed方法會(huì)返回true。至于應(yīng)該調(diào)用哪一種方法來(lái)關(guān)閉線程池,應(yīng)該由提交到線程池的任務(wù)特性決定,通常調(diào)用shutdown方法來(lái)關(guān)閉線程池,如果任務(wù)不一定要執(zhí)行完,則可以調(diào)用shutdownNow方法。
合理的配置線程池
要想合理地配置線程池,就必須首先分析任務(wù)特性,可以從以下幾個(gè)角度來(lái)分析。
1、任務(wù)的性質(zhì):CPU密集型任務(wù)、IO密集型任務(wù)和混合型任務(wù)。
2、任務(wù)的優(yōu)先級(jí):高、中和低。
3、任務(wù)的執(zhí)行時(shí)間:長(zhǎng)、中和短。
4、任務(wù)的依賴性:是否依賴其他系統(tǒng)資源,如數(shù)據(jù)庫(kù)連接。
性質(zhì)不同的任務(wù)可以用不同規(guī)模的線程池分開(kāi)處理。CPU密集型任務(wù)應(yīng)配置盡可能小的線程,如配置Ncpu+1個(gè)線程的線程池。由于IO密集型任務(wù)線程并不是一直在執(zhí)行任務(wù),則應(yīng)配置盡可能多的線程,如2*Ncpu。混合型的任務(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ì)列能增加系統(tǒng)的穩(wěn)定性和預(yù)警能力,可以根據(jù)需要設(shè)大一點(diǎn)兒,比如幾千。有時(shí)候我們系統(tǒng)里后臺(tái)任務(wù)線程池的隊(duì)列和線程池全滿了,不斷拋出拋棄任務(wù)的異常,通過(guò)排查發(fā)現(xiàn)是數(shù)據(jù)庫(kù)出現(xiàn)了問(wèn)題,導(dǎo)致執(zhí)行SQL變得非常緩慢,因?yàn)楹笈_(tái)任務(wù)線程池里的任務(wù)全是需要向數(shù)據(jù)庫(kù)查詢和插入數(shù)據(jù)的,所以導(dǎo)致線程池里的工作線程全部阻塞,任務(wù)積壓在線程池里。如果當(dāng)時(shí)我們?cè)O(shè)置成無(wú)界隊(duì)列,那么線程池的隊(duì)列就會(huì)越來(lái)越多,有可能會(huì)撐滿內(nèi)存,導(dǎo)致整個(gè)系統(tǒng)不可用,而不只是后臺(tái)任務(wù)出現(xiàn)問(wèn)題。當(dāng)然,我們的系統(tǒng)所有的任務(wù)是用單獨(dú)的服務(wù)器部署的,我們使用不同規(guī)模的線程池完成不同類型的任務(wù),但是出現(xiàn)這樣問(wèn)題時(shí)也會(huì)影響到其他任務(wù)。
線程池的監(jiān)控
如果在系統(tǒng)中大量使用線程池,則有必要對(duì)線程池進(jìn)行監(jiān)控,方便在出現(xiàn)問(wèn)題時(shí),可以根據(jù)線程池的使用狀況快速定位問(wèn)題??梢酝ㄟ^(guò)線程池提供的參數(shù)進(jìn)行監(jiān)控,在監(jiān)控線程池的時(shí)候可以使用以下屬性
taskCount:線程池需要執(zhí)行的任務(wù)數(shù)量。
completedTaskCount:線程池在運(yùn)行過(guò)程中已完成的任務(wù)數(shù)量,小于或等于taskCount。
largestPoolSize:線程池里曾經(jīng)創(chuàng)建過(guò)的最大線程數(shù)量。通過(guò)這個(gè)數(shù)據(jù)可以知道線程池是否曾經(jīng)滿過(guò)。如該數(shù)值等于線程池的最大大小,則表示線程池曾經(jīng)滿過(guò)。
getPoolSize:線程池的線程數(shù)量。如果線程池不銷(xiāo)毀的話,線程池里的線程不會(huì)自動(dòng)銷(xiāo)毀,所以這個(gè)大小只增不減。
getActiveCount:獲取活動(dòng)的線程數(shù)。
通過(guò)擴(kuò)展線程池進(jìn)行監(jiān)控??梢酝ㄟ^(guò)繼承線程池來(lái)自定義線程池,重寫(xiě)線程池的beforeExecute、afterExecute和terminated方法,也可以在任務(wù)執(zhí)行前、執(zhí)行后和線程池關(guān)閉前執(zhí)行一些代碼來(lái)進(jìn)行監(jiān)控。例如,監(jiān)控任務(wù)的平均執(zhí)行時(shí)間、最大執(zhí)行時(shí)間和最小執(zhí)行時(shí)間等。