newFixedThreadPool與newSingleThreadPool的區(qū)別
問題引出
newFixedThreadPool
與newSingleThreadPool
是jdk5之后呻右,java.util.concurrent
包下Executors
類中的兩個(gè)方法闷煤。前者是用于創(chuàng)建一個(gè)固定線程數(shù)量的線程池沃粗,后者是創(chuàng)建一個(gè)有且僅有一個(gè)線程的線程池绞旅。
機(jī)智的同學(xué)想必已經(jīng)看出一個(gè)問題了,一個(gè)線程不也就是固定數(shù)量的一個(gè)特例嗎宵溅,為什么還需要單獨(dú)添加一個(gè)方法呢?
第一層
二話不說,先擺上源碼别垮。
newFixedThreadPool在jdk8中的代碼如圖:
newSingleThreadPool在jdk8中的代碼如圖:
通過肉眼粗略的比較,發(fā)現(xiàn)FixPool中corePoolSize
和maximumPoolSize
傳入的都是nThreads
扎谎,而SinglePool中傳入的都是1碳想。
這時(shí)有的人就悟了,這不就相當(dāng)于是方法的重載嘛毁靶。一個(gè)是需要傳入nThreads
胧奔,而另一個(gè)就是不用傳參,默認(rèn)傳入1预吆。另外為了使用時(shí)更方便些龙填,把不傳參的這個(gè)方法給重命名了,變成更具象化的名稱newSingleThreadPool
拐叉。在我看來岩遗,這也許也是設(shè)計(jì)者Doug Lea
的其中之一個(gè)目的吧。
如果你也已經(jīng)看出這一點(diǎn)了凤瘦,那么恭喜你宿礁,你已經(jīng)來到了第一層。
第二層
再把源碼拿出來蔬芥,重新再找找不同梆靖。這是一個(gè)找茬的游戲。
其實(shí)很多小伙伴在最開始的時(shí)候就已經(jīng)發(fā)現(xiàn)了坝茎,我們眼睛又不瞎涤姊,明顯行數(shù)不一樣,在newSingleThreadPool
方法體的第一行嗤放,那么一大串的return new FinalizableDelegatedExecutorService
早就看到了思喊,還以為我們看不見嗎?次酌?
巧了不是恨课,我第一次看源碼的時(shí)候,就會(huì)<b>裝作</b>看不見岳服。畢竟在閱讀源碼的時(shí)候剂公,一個(gè)我認(rèn)為比較正確的思路是只看主干,忽略掉旁枝末節(jié)吊宋,只有在確實(shí)要對(duì)某一個(gè)模塊去做深入的時(shí)候纲辽,才會(huì)去了解內(nèi)部的細(xì)節(jié)。不然一個(gè)比較大的框架源碼有幾萬幾十萬幾百萬行代碼,哪里能啃的動(dòng)拖吼。
如果你想一次性直接一行行代碼讀下去鳞上,把整個(gè)框架學(xué)習(xí)一遍,那我估計(jì)你肯定一個(gè)框架都讀不好吊档,是個(gè)普通人真的沒有這種定力篙议。
舉個(gè)例子,一個(gè)剛開始學(xué)認(rèn)字的小朋友怠硼,說‘我不認(rèn)識(shí)字呀怎么辦鬼贱?’,‘不識(shí)字查字典呀’香璃,‘懂了这难,我這就從新華字典第一頁翻起,字字句句讀到最后一頁’增显。那么這個(gè)小朋友最終是讀完字段后雁佳,學(xué)會(huì)認(rèn)字了嗎,還是永遠(yuǎn)都在讀第一頁了解[ā][á][ǎ][à]
同云。
如果你能夠?qū)W會(huì)怎么什么時(shí)候該去粗讀,什么時(shí)候該去精讀堵腹,那么恭喜你炸站,已經(jīng)到達(dá)了第二層。
至此疚顷,我就可以繼續(xù)往下說了旱易,也怕誤人子弟。讓看了我的文章的人腿堤,以后讀源碼時(shí)鉆牛角尖阀坏,一個(gè)勁的要看到native方法才罷休。
第三層
再翻回來細(xì)讀笆檀。作為一個(gè)正常人忌堂,直接將nThreads
改寫為1不就完事了嗎,為什么還要去用FinalizableDelegatedExecutorService
來封裝一遍酗洒?凡事反常必有妖士修,看看jdk大佬到底為何要做此封裝。
上類關(guān)系圖樱衷。
其實(shí)在源碼里面直接類棋嘲,查看類的父類和實(shí)現(xiàn)的接口也很方便,不過為了在文章中能夠說清楚矩桂,還是擺出關(guān)系圖更直觀些沸移,也能省一些筆墨。
FinalizableDelegatedExecutorService
的父類是DelegatedExecutorService
,兩者雖然都是Executors
的內(nèi)部類,DelegatedExecutorService
和ThreadPoolExecutor
一樣雹锣,都是繼承自AbstractExecutorService
流妻。說白了他們兩是兄弟關(guān)系。
再看一樣AbstractExecutorService
中實(shí)現(xiàn)的方法笆制,只有一些提交任務(wù)之類的方法绅这。而熟悉ThreadPoolExecutor
的朋友都知道,內(nèi)部是有比較豐富的方法去操作線程池的核心參數(shù)的在辆。
回頭再看看DelegatedExecutorService
中证薇,竟然只實(shí)現(xiàn)了任務(wù)提交、關(guān)閉等方法匆篓。
兩者比較之下浑度,最終就可以發(fā)現(xiàn),對(duì)于FinalizableDelegatedExecutorService
來說鸦概,關(guān)閉了對(duì)線程池參數(shù)的修改權(quán)限箩张,因此在使用中,不會(huì)將固定的線程數(shù)1誤修改為非1的情況窗市。防止出現(xiàn)明明設(shè)置了單個(gè)線程先慷,在使用時(shí)卻出現(xiàn)了線程池中存在多個(gè)線程的問題。
demo如下:
至此咨察,能夠看懂如此設(shè)計(jì)是為什么论熙,能實(shí)現(xiàn)什么功能。那么已經(jīng)到達(dá)了第三層摄狱。
第四層
看懂了為什么這么做脓诡,這只是術(shù),還更應(yīng)該了解法媒役。
關(guān)于‘道祝谚、法、術(shù)酣衷、器交惯、勢(shì)’的內(nèi)容可以看我其他的筆記。
法即法則鸥诽、思想商玫。了解為什么這樣設(shè)計(jì),依據(jù)的設(shè)計(jì)思想是什么牡借?學(xué)會(huì)這些才能帶你進(jìn)入更深的層次拳昌。
在我淺顯的看來,newSingleThreadPool
的設(shè)計(jì)是一個(gè)典型的體現(xiàn)了單一職責(zé)原則和接口隔離原則钠龙。
此方法只能提供創(chuàng)建固定線程池的作用炬藤,職責(zé)單一御铃。
對(duì)于線程核心參數(shù)不操作的情況,通過面向?qū)ο蟮脑O(shè)計(jì)沈矿,將這些方法都隔離出去上真,沒有操作的權(quán)限。因此也體現(xiàn)了接口隔離的原則羹膳。
除了設(shè)計(jì)原則睡互,在阿里的開發(fā)規(guī)范中,是禁止我們使用Executors
類去直接創(chuàng)建線程的陵像。
因?yàn)樵谧铋_始的構(gòu)造方法中我們就已經(jīng)發(fā)現(xiàn)就珠,使用的隊(duì)列是一個(gè)沒有指定大小的無界隊(duì)列,在極端情況下會(huì)出現(xiàn)oom醒颖。
所以說了這么久newSingleThreadPool
的優(yōu)點(diǎn)妻怎,在實(shí)際的使用場(chǎng)景中,并用不到這里的方法泞歉。然而我們?cè)趯?shí)際工作場(chǎng)景中逼侦,多多少少在特定的業(yè)務(wù)場(chǎng)景下,要用到單線程的線程池腰耙。
工作中如果直接使用new ThreadPoolExecutor()
的方式去創(chuàng)建線程池時(shí)榛丢,應(yīng)該完善文檔,提示不要去修改線程數(shù)量沟优。
更合理的方式應(yīng)該是在第四層法的指導(dǎo)下涕滋,通過一些技術(shù)手段,模仿Executors
這個(gè)類挠阁,實(shí)現(xiàn)一個(gè)器即工具類,通過代碼的手段直接從根本上規(guī)避單線程池被修改線程數(shù)量而導(dǎo)致的問題溯饵。
如果也看懂了設(shè)計(jì)思想侵俗,也能夠讓這種思想指導(dǎo)以后的工作,那么說明你的學(xué)習(xí)是有用的丰刊,不再是那種自己感動(dòng)自己的行為隘谣。此刻,你已經(jīng)到達(dá)了第四層啄巧。
第五層
還沒到第五層寻歧,只能等大神們幫忙蓋樓搭梯,看看有沒有人能領(lǐng)我去第五層瞧瞧高處的風(fēng)景秩仆。