?要使用一門技術(shù)肯定要先了解它,要使用線程池也一定要了解線程池使用的情況和基本參數(shù)永部。
使用線程池有優(yōu)勢(shì)與限制場(chǎng)景
利用Executor框架可以解耦任務(wù)的提交和任務(wù)的執(zhí)行,使得任務(wù)的提交和任務(wù)的執(zhí)行更加靈活呐矾,同時(shí)也簡(jiǎn)化了代碼苔埋,不過(guò)在有些情況下因?yàn)槿蝿?wù)不適合解耦。
比如一個(gè)任務(wù)依賴另一個(gè)任務(wù)的執(zhí)行蜒犯,或者任務(wù)只能在單線程中執(zhí)行(為了確保執(zhí)行順序)组橄,在比如對(duì)一些響應(yīng)要求比較高的任務(wù)都不適合放到線程池中執(zhí)行。
一般來(lái)說(shuō)只有任務(wù)是同類型的并且相互獨(dú)立罚随,線程池的性能才能達(dá)到最佳玉工。
線程池大小設(shè)置
線程池大小的設(shè)置也是一個(gè)需要注意的問(wèn)題,線程池設(shè)置過(guò)大會(huì)出現(xiàn)大量線程在比較少的CPU和內(nèi)存資源上發(fā)生競(jìng)爭(zhēng)淘菩,會(huì)導(dǎo)致更高的內(nèi)存使用量遵班,還有可能耗盡資源,但是如果線程池過(guò)小潮改,那么將導(dǎo)致許多空閑的處理器無(wú)法執(zhí)行工作狭郑,更多的任務(wù)處于等待狀態(tài)響應(yīng)變慢,從而降低吞吐率汇在。
線程池大小設(shè)置要考慮服務(wù)器的CPU翰萨、內(nèi)存、計(jì)算密集型還是I/O密集型等方面糕殉,尤其是計(jì)算密集型和I/O密集型屬于不同類型的任務(wù)亩鬼,他們最好分成多個(gè)線程池不要放到一個(gè)線程池中。
計(jì)算密集型運(yùn)行一般較快阿蝶,基本不會(huì)出現(xiàn)阻塞雳锋,線程可以很快處理完成馬上執(zhí)行下一個(gè)任務(wù),所以線程池大小可以設(shè)置成服務(wù)器CPU核數(shù)+1羡洁,節(jié)省資源的同時(shí)也能保證充分利用CPU玷过。
對(duì)于包含I/O或者其他阻塞操作的任務(wù),由于線程不會(huì)一直執(zhí)行,為了支持更多的任務(wù)線程池的數(shù)量設(shè)置的就會(huì)更大冶匹,當(dāng)然要更加準(zhǔn)確的設(shè)置線程池?cái)?shù)量就要估算任務(wù)的等待時(shí)間和計(jì)算時(shí)間的比值习劫。
如果任務(wù)的執(zhí)行還需要通過(guò)獲取其他線程池資源咆瘟,比如需要連接數(shù)據(jù)庫(kù)就需要數(shù)據(jù)庫(kù)線程池嚼隘,那么數(shù)據(jù)庫(kù)線程池的數(shù)量也是限制任務(wù)線程的因素之一。
線程池基本參數(shù)
線程最通用的初始化方式如下圖:
這是最基礎(chǔ)的線程池初始化方式袒餐,包含了設(shè)置線程池需要的基礎(chǔ)參數(shù)飞蛹,主要參數(shù)功能解釋如下:
corePoolSize:線程池核心線程數(shù),當(dāng)沒(méi)有任務(wù)執(zhí)行時(shí)線程池大小灸眼,當(dāng)工作隊(duì)列滿了才會(huì)創(chuàng)建超過(guò)這個(gè)數(shù)量的線程卧檐;
maximumPoolSize:最大線程數(shù),線程池能擁有的最大線程數(shù)量焰宣;
keepAliveTime:活躍時(shí)間霉囚,當(dāng)一個(gè)線程空閑超過(guò)這個(gè)時(shí)間,并且線程池線程數(shù)超過(guò)了核心線程數(shù)匕积,這個(gè)線程將被回收盈罐;
通過(guò)調(diào)節(jié)上面這三個(gè)參數(shù)可以控制資源的使用,比如newFixedThreadPool創(chuàng)建的線程池就是把corePoolSize與maximumPoolSize設(shè)置為相同值闪唆,并且永遠(yuǎn)不會(huì)超時(shí)盅粪,這樣線程池中的線程數(shù)量永遠(yuǎn)不會(huì)改變。
newCachedThreadPool是把corePoolSize設(shè)置為0悄蕾,maximumPoolSize設(shè)置為Integer的最大值票顾,keepAliveTime設(shè)置為一分鐘,線程池的數(shù)量可以根據(jù)任務(wù)數(shù)擴(kuò)展或收縮帆调,任務(wù)多就創(chuàng)建線程奠骄,沒(méi)有任務(wù)則回收所有線程。
workQueue是一個(gè)阻塞隊(duì)列番刊,用來(lái)保存等待執(zhí)行的任務(wù)戚揭,排隊(duì)基本方法有三種:無(wú)界隊(duì)列、有界隊(duì)列撵枢、同步移交民晒。
newFixedThreadPool和newSingleThreadExecutor默認(rèn)使用無(wú)界隊(duì)列LinkedBlockingQueue,如果所有工作線程都處于忙碌狀態(tài),那么任務(wù)將在隊(duì)列中等候锄禽,如果任務(wù)持續(xù)提交潜必,隊(duì)列將無(wú)限制增加。
隊(duì)列無(wú)限增加存在一定的風(fēng)險(xiǎn)沃但,比如任務(wù)提交太多太快磁滚,隊(duì)列中太多會(huì)造成內(nèi)存溢出,保證安全的辦法是使用有界隊(duì)列,比如ArrayBlockingQueue,但是使用有界隊(duì)列會(huì)出現(xiàn)新問(wèn)題垂攘,如果等待隊(duì)列滿了再繼續(xù)執(zhí)行任務(wù)該怎么處理维雇?
這里就要說(shuō)最后一個(gè)參數(shù)了“RejectedExecutionHandler handler”拒絕策略,拒絕策略主要有四種AbortPolicy(中止)晒他、CallerRunsPolicy(調(diào)用者運(yùn)行)吱型、DiscardPolicy(拋棄)、DiscardestPolicy(拋棄最舊的)陨仅;
AbortPolicy(中止):默認(rèn)拒絕策略津滞,提交任務(wù)時(shí)如果無(wú)法保存到隊(duì)列中時(shí)會(huì)拋出RejectedExecutionException。由調(diào)用捕獲并處理灼伤;
DiscardPolicy(拋棄):直接拋棄這個(gè)任務(wù)触徐;
DiscardestPolicy(拋棄最舊的):會(huì)拋棄最前面的任務(wù)也就是即將執(zhí)行的任務(wù),然后嘗試重新提交當(dāng)前任務(wù)狐赡;
CallerRunsPolicy(調(diào)用者運(yùn)行):將任務(wù)回退到調(diào)用者撞鹉;
還有一個(gè)參數(shù)“ThreadFactory threadFactory”創(chuàng)建工作線程的工廠,也就是線程池支持我們自定義創(chuàng)建線程的方式颖侄,這樣我們就可以做很多事情鸟雏,比如設(shè)置線程池創(chuàng)建線程的線程名字,還可以做一些統(tǒng)計(jì)发皿、日志等崔慧。
線程池的擴(kuò)展
ThreadPoolExecutor還提供了幾個(gè)可以重寫的方法”beforeExecute(Thread t, Runnable r)”、“afterExecute(Runnable r, Throwable t)”穴墅、“terminated()”惶室。
這幾個(gè)方法都很見(jiàn)名知意,beforeExecute拋出異常任務(wù)不會(huì)被執(zhí)行玄货,afterExecute也不執(zhí)行皇钞。任務(wù)不管是正常返回還是拋出一個(gè)異常都會(huì)執(zhí)行afterExecute方法,當(dāng)線程池完成關(guān)閉操作后會(huì)執(zhí)行terminated方法松捉,通過(guò)重寫這幾個(gè)方法可以添加日志夹界、計(jì)時(shí)、監(jiān)視隘世、統(tǒng)計(jì)等信息可柿。
總結(jié)
ThreadPoolExecutor作為多線程的基礎(chǔ),一定要了解它的基本功能丙者,學(xué)習(xí)了ThreadPoolExecutor的最基礎(chǔ)參數(shù)實(shí)際上也可以了解它的一些基本的工作流程复斥,這些最基礎(chǔ)的知識(shí)也是面試中經(jīng)常問(wèn)到的問(wèn)題。
Java程序員日常學(xué)習(xí)筆記械媒,如理解有誤歡迎各位交流討論目锭!