java----線程池

  • 什么是線程池
  • 為什么要使用線程池
  • 線程池的處理邏輯
  • 如何使用線程池
  • 如何合理配置線程池的大小
  • 結(jié)語(yǔ)

什么是線程池

線程池菩鲜,顧名思義就是裝線程的池子。其用途是為了幫我們重復(fù)管理線程集晚,避免創(chuàng)建大量的線程增加開(kāi)銷(xiāo)晌砾,提高響應(yīng)速度攀甚。

image.png

為什么要用線程池

作為一個(gè)嚴(yán)謹(jǐn)?shù)墓コ仟{吞瞪,不會(huì)希望別人看到我們的代碼就開(kāi)始吐槽馁启,new Thread().start()會(huì)讓代碼看起來(lái)混亂臃腫,并且不好管理和維護(hù)芍秆,那么我們就需要用到了線程池惯疙。

在編程中經(jīng)常會(huì)使用線程來(lái)異步處理任務(wù)翠勉,但是每個(gè)線程的創(chuàng)建和銷(xiāo)毀都需要一定的開(kāi)銷(xiāo)。如果每次執(zhí)行一個(gè)任務(wù)都需要開(kāi)一個(gè)新線程去執(zhí)行霉颠,則這些線程的創(chuàng)建和銷(xiāo)毀將消耗大量的資源对碌;并且線程都是“各自為政”的,很難對(duì)其進(jìn)行控制蒿偎,更何況有一堆的線程在執(zhí)行俭缓。線程池為我們做的,就是線程創(chuàng)建之后為我們保留酥郭,當(dāng)我們需要的時(shí)候直接拿來(lái)用,省去了重復(fù)創(chuàng)建銷(xiāo)毀的過(guò)程愿吹。

線程池的處理邏輯

線程池ThreadPoolExecutor構(gòu)造函數(shù)

//五個(gè)參數(shù)的構(gòu)造函數(shù)
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue)

//六個(gè)參數(shù)的構(gòu)造函數(shù)-1
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory)

//六個(gè)參數(shù)的構(gòu)造函數(shù)-2
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler)

//七個(gè)參數(shù)的構(gòu)造函數(shù)
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) 

雖然參數(shù)多不从,只是看著嚇人,其實(shí)很好理解犁跪,下面會(huì)一一解答椿息。

我們拿最多參數(shù)的來(lái)說(shuō):

1.corePoolSize -> 該線程池中核心線程數(shù)最大值

核心線程:在創(chuàng)建完線程池之后,核心線程先不創(chuàng)建坷衍,在接到任務(wù)之后創(chuàng)建核心線程寝优。并且會(huì)一直存在于線程池中(即使這個(gè)線程啥都不干),有任務(wù)要執(zhí)行時(shí)枫耳,如果核心線程沒(méi)有被占用乏矾,會(huì)優(yōu)先用核心線程執(zhí)行任務(wù)。數(shù)量一般情況下設(shè)置為CPU核數(shù)的二倍即可迁杨。

2.maximumPoolSize -> 該線程池中線程總數(shù)最大值

線程總數(shù)=核心線程數(shù)+非核心線程數(shù)

非核心線程:簡(jiǎn)單理解钻心,即核心線程都被占用,但還有任務(wù)要做铅协,就創(chuàng)建非核心線程

3.keepAliveTime -> 非核心線程閑置超時(shí)時(shí)長(zhǎng)

這個(gè)參數(shù)可以理解為捷沸,任務(wù)少,但池中線程多狐史,非核心線程不能白養(yǎng)著痒给,超過(guò)這個(gè)時(shí)間不工作的就會(huì)被干掉,但是核心線程會(huì)保留骏全。

4.TimeUnit -> keepAliveTime的單位

TimeUnit是一個(gè)枚舉類(lèi)型苍柏,其包括:
NANOSECONDS : 1微毫秒 = 1微秒 / 1000
MICROSECONDS : 1微秒 = 1毫秒 / 1000
MILLISECONDS : 1毫秒 = 1秒 /1000
SECONDS : 秒
MINUTES : 分
HOURS : 小時(shí)
DAYS : 天

5.BlockingQueue workQueue -> 線程池中的任務(wù)隊(duì)列

默認(rèn)情況下,任務(wù)進(jìn)來(lái)之后先分配給核心線程執(zhí)行姜贡,核心線程如果都被占用序仙,并不會(huì)立刻開(kāi)啟非核心線程執(zhí)行任務(wù),而是將任務(wù)插入任務(wù)隊(duì)列等待執(zhí)行鲁豪,核心線程會(huì)從任務(wù)隊(duì)列取任務(wù)來(lái)執(zhí)行潘悼,任務(wù)隊(duì)列可以設(shè)置最大值律秃,一旦插入的任務(wù)足夠多,達(dá)到最大值治唤,才會(huì)創(chuàng)建非核心線程執(zhí)行任務(wù)棒动。

常見(jiàn)的workQueue有四種:

1.SynchronousQueue:這個(gè)隊(duì)列接收到任務(wù)的時(shí)候,會(huì)直接提交給線程處理宾添,而不保留它船惨,如果所有線程都在工作怎么辦?那就新建一個(gè)線程來(lái)處理這個(gè)任務(wù)缕陕!所以為了保證不出現(xiàn)<線程數(shù)達(dá)到了maximumPoolSize而不能新建線程>的錯(cuò)誤粱锐,使用這個(gè)類(lèi)型隊(duì)列的時(shí)候,maximumPoolSize一般指定成Integer.MAX_VALUE扛邑,即無(wú)限大

2.LinkedBlockingQueue:這個(gè)隊(duì)列接收到任務(wù)的時(shí)候怜浅,如果當(dāng)前已經(jīng)創(chuàng)建的核心線程數(shù)小于線程池的核心線程數(shù)上限,則新建線程(核心線程)處理任務(wù)蔬崩;如果當(dāng)前已經(jīng)創(chuàng)建的核心線程數(shù)等于核心線程數(shù)上限恶座,則進(jìn)入隊(duì)列等待。由于這個(gè)隊(duì)列沒(méi)有最大值限制沥阳,即所有超過(guò)核心線程數(shù)的任務(wù)都將被添加到隊(duì)列中跨琳,這也就導(dǎo)致了maximumPoolSize的設(shè)定失效,因?yàn)榭偩€程數(shù)永遠(yuǎn)不會(huì)超過(guò)corePoolSize

3.ArrayBlockingQueue:可以限定隊(duì)列的長(zhǎng)度桐罕,接收到任務(wù)的時(shí)候脉让,如果沒(méi)有達(dá)到corePoolSize的值,則新建線程(核心線程)執(zhí)行任務(wù)功炮,如果達(dá)到了侠鳄,則入隊(duì)等候,如果隊(duì)列已滿死宣,則新建線程(非核心線程)執(zhí)行任務(wù)伟恶,又如果總線程數(shù)到了maximumPoolSize,并且隊(duì)列也滿了毅该,則發(fā)生錯(cuò)誤博秫,或是執(zhí)行實(shí)現(xiàn)定義好的飽和策略

4.DelayQueue:隊(duì)列內(nèi)元素必須實(shí)現(xiàn)Delayed接口,這就意味著你傳進(jìn)去的任務(wù)必須先實(shí)現(xiàn)Delayed接口眶掌。這個(gè)隊(duì)列接收到任務(wù)時(shí)挡育,首先先入隊(duì),只有達(dá)到了指定的延時(shí)時(shí)間朴爬,才會(huì)執(zhí)行任務(wù)

6.ThreadFactory threadFactory -> 創(chuàng)建線程的工廠

可以用線程工廠給每個(gè)創(chuàng)建出來(lái)的線程設(shè)置名字即寒。一般情況下無(wú)須設(shè)置該參數(shù)。

7.RejectedExecutionHandler handler -> 飽和策略

這是當(dāng)任務(wù)隊(duì)列和線程池都滿了時(shí)所采取的應(yīng)對(duì)策略,默認(rèn)是AbordPolicy母赵, 表示無(wú)法處理新任務(wù)逸爵,并拋出 RejectedExecutionException 異常。此外還有3種策略凹嘲,它們分別如下师倔。
(1)CallerRunsPolicy:用調(diào)用者所在的線程來(lái)處理任務(wù)。此策略提供簡(jiǎn)單的反饋控制機(jī)制周蹭,能夠減緩新任務(wù)的提交速度趋艘。
(2)DiscardPolicy:不能執(zhí)行的任務(wù),并將該任務(wù)刪除凶朗。
(3)DiscardOldestPolicy:丟棄隊(duì)列最近的任務(wù)瓷胧,并執(zhí)行當(dāng)前的任務(wù)。

別暈棚愤,接下來(lái)上圖搓萧,相信結(jié)合圖你能大徹大悟~

image.png

如何使用線程池

說(shuō)了半天原理,接下來(lái)就要用了遇八,java為我們提供了4種線程池FixedThreadPoolCachedThreadPool耍休、SingleThreadExecutor刃永、ScheduledThreadPool,幾乎可以滿足我們大部分的需要了:

1.FixedThreadPool

可重用固定線程數(shù)的線程池羊精,超出的線程會(huì)在隊(duì)列中等待斯够,在Executors類(lèi)中我們可以找到創(chuàng)建方式:

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

FixedThreadPoolcorePoolSizemaximumPoolSize都設(shè)置為參數(shù)nThreads,也就是只有固定數(shù)量的核心線程喧锦,不存在非核心線程读规。keepAliveTime為0L表示多余的線程立刻終止,因?yàn)椴粫?huì)產(chǎn)生多余的線程燃少,所以這個(gè)參數(shù)是無(wú)效的束亏。FixedThreadPool的任務(wù)隊(duì)列采用的是LinkedBlockingQueue。

image.png

創(chuàng)建線程池的方法阵具,在我們的程序中只需要碍遍,后面其他種類(lèi)的同理:

public static void main(String[] args) {
        // 參數(shù)是要線程池的線程最大值
        ExecutorService executorService = Executors.newFixedThreadPool(10);
}

2.CachedThreadPool

CachedThreadPool是一個(gè)根據(jù)需要?jiǎng)?chuàng)建線程的線程池

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

CachedThreadPoolcorePoolSize是0,maximumPoolSize是Int的最大值阳液,也就是說(shuō)CachedThreadPool沒(méi)有核心線程怕敬,全部都是非核心線程,并且沒(méi)有上限帘皿。keepAliveTime是60秒东跪,就是說(shuō)空閑線程等待新任務(wù)60秒,超時(shí)則銷(xiāo)毀。此處用到的隊(duì)列是阻塞隊(duì)列SynchronousQueue,這個(gè)隊(duì)列沒(méi)有緩沖區(qū)虽填,所以其中最多只能存在一個(gè)元素,有新的任務(wù)則阻塞等待丁恭。

image.png

3.SingleThreadExecutor

SingleThreadExecutor是使用單個(gè)線程工作的線程池。其創(chuàng)建源碼如下:

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

我們可以看到總線程數(shù)和核心線程數(shù)都是1卤唉,所以就只有一個(gè)核心線程涩惑。該線程池才用鏈表阻塞隊(duì)列LinkedBlockingQueue,先進(jìn)先出原則桑驱,所以保證了任務(wù)的按順序逐一進(jìn)行竭恬。

image.png

4.ScheduledThreadPool

ScheduledThreadPool是一個(gè)能實(shí)現(xiàn)定時(shí)和周期性任務(wù)的線程池,它的創(chuàng)建源碼如下:

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

這里創(chuàng)建了ScheduledThreadPoolExecutor熬的,繼承自ThreadPoolExecutor痊硕,主要用于定時(shí)延時(shí)或者定期處理任務(wù)。ScheduledThreadPoolExecutor的構(gòu)造如下:

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }

可以看出corePoolSize是傳進(jìn)來(lái)的固定值押框,maximumPoolSize無(wú)限大岔绸,因?yàn)椴捎玫年?duì)列DelayedWorkQueue是無(wú)解的,所以maximumPoolSize參數(shù)無(wú)效橡伞。該線程池執(zhí)行如下:

image.png

當(dāng)執(zhí)行scheduleAtFixedRate或者scheduleWithFixedDelay方法時(shí)盒揉,會(huì)向DelayedWorkQueue添加一個(gè)實(shí)現(xiàn)RunnableScheduledFuture接口的ScheduledFutureTask(任務(wù)的包裝類(lèi)),并會(huì)檢查運(yùn)行的線程是否達(dá)到corePoolSize兑徘。如果沒(méi)有則新建線程并啟動(dòng)ScheduledFutureTask刚盈,然后去執(zhí)行任務(wù)。如果運(yùn)行的線程達(dá)到了corePoolSize時(shí)挂脑,則將任務(wù)添加到DelayedWorkQueue中藕漱。DelayedWorkQueue會(huì)將任務(wù)進(jìn)行排序,先要執(zhí)行的任務(wù)會(huì)放在隊(duì)列的前面崭闲。在跟此前介紹的線程池不同的是肋联,當(dāng)執(zhí)行完任務(wù)后,會(huì)將ScheduledFutureTask中的time變量改為下次要執(zhí)行的時(shí)間并放回到DelayedWorkQueue中刁俭。

如何合理配置線程池的大小

一般需要根據(jù)任務(wù)的類(lèi)型來(lái)配置線程池大虚先浴:
如果是CPU密集型任務(wù),就需要盡量壓榨CPU牍戚,參考值可以設(shè)為 NCPU+1
如果是IO密集型任務(wù)沙兰,參考值可以設(shè)置為2*NCPU
當(dāng)然,這只是一個(gè)參考值翘魄,具體的設(shè)置還需要根據(jù)實(shí)際情況進(jìn)行調(diào)整鼎天,比如可以先將線程池大小設(shè)置為參考值,再觀察任務(wù)運(yùn)行情況和系統(tǒng)負(fù)載暑竟、資源利用率來(lái)進(jìn)行適當(dāng)調(diào)整斋射。

結(jié)語(yǔ)

java為我們提供的線程池就介紹到這了育勺,墻裂建議大家還是動(dòng)手去敲一敲,畢竟實(shí)踐過(guò)心里才有底罗岖。

作者:愛(ài)擼鐵的攻城獅
鏈接:https://juejin.im/post/5a743c526fb9a063557d7eba
來(lái)源:掘金
著作權(quán)歸作者所有涧至。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處桑包。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末南蓬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子哑了,更是在濱河造成了極大的恐慌赘方,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弱左,死亡現(xiàn)場(chǎng)離奇詭異窄陡,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)拆火,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)跳夭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人们镜,你說(shuō)我怎么就攤上這事币叹。” “怎么了模狭?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵颈抚,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我胞皱,道長(zhǎng)邪意,這世上最難降的妖魔是什么九妈? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任反砌,我火速辦了婚禮,結(jié)果婚禮上萌朱,老公的妹妹穿的比我還像新娘宴树。我一直安慰自己,他們只是感情好晶疼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布酒贬。 她就那樣靜靜地躺著,像睡著了一般翠霍。 火紅的嫁衣襯著肌膚如雪锭吨。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天寒匙,我揣著相機(jī)與錄音零如,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛考蕾,可吹牛的內(nèi)容都是我干的祸憋。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼肖卧,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蚯窥!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起塞帐,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤拦赠,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后壁榕,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體矛紫,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年牌里,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了颊咬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡牡辽,死狀恐怖喳篇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情态辛,我是刑警寧澤麸澜,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站奏黑,受9級(jí)特大地震影響炊邦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜熟史,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一馁害、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蹂匹,春花似錦碘菜、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至履植,卻和暖如春计雌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背玫霎。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工凿滤, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留传泊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓鸭巴,卻偏偏與公主長(zhǎng)得像眷细,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鹃祖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容