溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

使用Java無界隊(duì)列的線程池會(huì)怎么樣

發(fā)布時(shí)間:2022-01-04 17:03:17 來源:億速云 閱讀:318 作者:iii 欄目:編程語言

本篇內(nèi)容主要講解“使用Java無界隊(duì)列的線程池會(huì)怎么樣”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“使用Java無界隊(duì)列的線程池會(huì)怎么樣”吧!

(1)背景引入

今天跟大家聊一個(gè)互聯(lián)網(wǎng)大廠的Java面試題:使用無界隊(duì)列的線程池會(huì)導(dǎo)致內(nèi)存飆升嗎?

因?yàn)樵诿婊ヂ?lián)網(wǎng)大廠的時(shí)候,一定會(huì)問并發(fā),問并發(fā)的時(shí)候一定會(huì)問到線程池,問到線程池一定會(huì)問構(gòu)造線程池的一些參數(shù)的含義。

然后,有一些面試官會(huì)就線程池的具體場(chǎng)景,問一些可能會(huì)遇到的問題。

所以,在這里就可能有上述那樣一個(gè)面試中的問題,算是Java面試?yán)锵鄬?duì)來說高階一點(diǎn)的。

我相信大家一定起碼知道線程池是個(gè)什么東西的。簡(jiǎn)單來說,就是維護(hù)一個(gè)池子,池子里面放了很多的線程。

然后來一個(gè)任務(wù),某個(gè)線程就獲取這個(gè)任務(wù)來執(zhí)行,任務(wù)執(zhí)行完之后線程是不會(huì)釋放掉的,而是停留在線程池里繼續(xù)等待下一個(gè)任務(wù)。

這樣的一個(gè)好處是你沒必要自己手動(dòng)頻繁的創(chuàng)建和銷毀線程,畢竟線程是較重的資源,頻繁的創(chuàng)建和銷毀對(duì)系統(tǒng)性能是沒好處的。

我們看看下面的圖,回顧一下線程池的含義。

使用Java無界隊(duì)列的線程池會(huì)怎么樣

(2)線程池是如何構(gòu)造的?

那么平時(shí)在Java里寫代碼的時(shí)候,大家記得不記得線程池是如何構(gòu)造出來的呢?

是不是類似下面那樣的代碼,比如說我們構(gòu)造一個(gè)線程數(shù)量固定的一個(gè)線程池:

使用Java無界隊(duì)列的線程池會(huì)怎么樣

那么Executors.newFixedThreadPool(10)內(nèi)部到底又是如何構(gòu)造出來線程池的呢?

其實(shí)很簡(jiǎn)單,翻開JDK源碼就可以看到里面的代碼如下:

使用Java無界隊(duì)列的線程池會(huì)怎么樣

簡(jiǎn)單來說,就是構(gòu)造了一個(gè)ThreadPoolExecutor對(duì)象實(shí)例,你大致就認(rèn)為他是一個(gè)線程池吧,傳入了一些參數(shù),這些參數(shù)大致包含了:

  1  ·   corePoolSize

  2  ·   maximumPoolSize

  3  ·   keepAliveTime

  4  · workQueue

假如說我們構(gòu)造線程池傳入的線程數(shù)量是10,那么在這里,corePoolSize和maximumSize都是10,keepAliveTime默認(rèn)就是0,workQueue是一個(gè)無界的LinkedBlockingQueue。

接下來,我們具體來看看構(gòu)造一個(gè)線程池傳入一些參數(shù)之后,具體這個(gè)線程池的運(yùn)行原理是什么。

(3)線程池的運(yùn)行原理

簡(jiǎn)單來說,剛開始的時(shí)候其實(shí)線程池里是空的,就是一個(gè)線程都沒有的,如下圖所示。

使用Java無界隊(duì)列的線程池會(huì)怎么樣

接著如果你使用線程池提交一個(gè)任務(wù)進(jìn)去,希望由線程池里的一個(gè)線程來執(zhí)行,如下代碼所示,就是提交一個(gè)任務(wù):

使用Java無界隊(duì)列的線程池會(huì)怎么樣

這個(gè)時(shí)候,線程池會(huì)先看一下,現(xiàn)在池子里的線程數(shù)量有沒有有達(dá)到corePoolSize指定的數(shù)量。

現(xiàn)在線程池里的線程數(shù)量是0,然后corePoolSize是10,那么肯定沒達(dá)到了,所以直接會(huì)在線程池里創(chuàng)建一個(gè)線程出來然后執(zhí)行這個(gè)任務(wù),如下圖。

使用Java無界隊(duì)列的線程池會(huì)怎么樣

接著假如說,這個(gè)線程處理完一個(gè)任務(wù)了,那么此時(shí)線程是不會(huì)被銷毀的,他會(huì)一直等待下一個(gè)提交過來的任務(wù)。

那么,到底是怎么等待的呢?

很簡(jiǎn)單,線程池會(huì)搭配一個(gè)workQueue,比如這里搭配的就是一個(gè)無界的LinkedBlockingQueue,幾乎可以無限量放入任務(wù)。

然后那個(gè)線程處理完一個(gè)任務(wù)之后,就會(huì)用阻塞的方式嘗試從任務(wù)隊(duì)列里獲取任務(wù),如果隊(duì)列是空的,他就會(huì)阻塞卡在那兒不動(dòng),直到有人放一個(gè)任務(wù)到隊(duì)列里,他才會(huì)獲取到一個(gè)任務(wù)然后繼續(xù)執(zhí)行,循環(huán)往復(fù),如下圖。

使用Java無界隊(duì)列的線程池會(huì)怎么樣

接著再次提交任務(wù),線程池一判斷發(fā)現(xiàn),誒?好像線程數(shù)量才只有1個(gè),完全比corePoolSize(10個(gè))要小,那么繼續(xù)直接在池子里創(chuàng)建一個(gè)線程,然后處理這個(gè)任務(wù),處理完了繼續(xù)嘗試從workQueue里阻塞式獲取任務(wù)。

一直重復(fù)上面的操作,直到線程池里有10個(gè)線程了,達(dá)到了corePoolSize指定的數(shù)量,如下圖。

使用Java無界隊(duì)列的線程池會(huì)怎么樣

這個(gè)時(shí)候你如果再提交任務(wù),他一下子發(fā)現(xiàn),誒?不對(duì)啊,線程池里已經(jīng)有10個(gè)線程了,跟corePoolSize指定的線程數(shù)量一樣了。

那么現(xiàn)在,我就不需要?jiǎng)?chuàng)建任何一個(gè)額外的線程了,現(xiàn)在你只要提交任務(wù),全部直接入隊(duì)到workQueue里就好。

此時(shí)線程池里的線程都阻塞式在workQueue上等待獲取任務(wù),有一個(gè)任務(wù)進(jìn)來就會(huì)喚醒一個(gè)線程來處理這個(gè)任務(wù),處理完了任務(wù)再次阻塞在workQueue上嘗試獲取下一個(gè)任務(wù),如下圖所示這個(gè)意思。

使用Java無界隊(duì)列的線程池會(huì)怎么樣

這里我們看到他用的是一個(gè)無界的LinkedBlockingQueue,但是假如說他用的是一個(gè)有界的隊(duì)列呢?

比如說限定好了隊(duì)列最多只能放10個(gè)任務(wù),那么假如說,線程池里的線程來不及處理任務(wù)了,然后隊(duì)列一下子放滿了10個(gè)任務(wù)。

此時(shí)就會(huì)出現(xiàn)任務(wù)入隊(duì)的失敗,因?yàn)殛?duì)列滿了,無法入隊(duì)。

然后就會(huì)嘗試再次在線程池里創(chuàng)建線程,這個(gè)時(shí)候就會(huì)一直創(chuàng)建線程直到線程池里的線程數(shù)量達(dá)到maximumPoolSize指定的數(shù)量為止。

雖然這里fixed線程池默認(rèn)corePoolSize和maximumPoolSize的數(shù)量都是一致的,但是可以假設(shè)此時(shí)maximumPoolSize的數(shù)量是20呢?

那么就會(huì)繼續(xù)創(chuàng)建線程,直到線程數(shù)量達(dá)到20個(gè),然后用額外創(chuàng)建的10個(gè)線程在隊(duì)列滿的情況下,繼續(xù)處理任務(wù)。

整個(gè)過程,如下圖所示:

使用Java無界隊(duì)列的線程池會(huì)怎么樣

接著萬一隊(duì)列滿了,然后線程池的線程數(shù)量達(dá)到了maximumPoolSize指定的數(shù)量了,你額外創(chuàng)建線程都無法創(chuàng)建了,此時(shí)會(huì)如何呢?

答案是:會(huì)reject掉,不讓你繼續(xù)提交任務(wù)了,此時(shí)默認(rèn)的就是拋出一個(gè)異常。

那么,在上圖中額外創(chuàng)建出來的,超出corePoolSize的那些線程呢?

他們一旦創(chuàng)建出來之后,會(huì)發(fā)現(xiàn)線程池?cái)?shù)量已經(jīng)超過corePoolSize了,此時(shí)他們會(huì)嘗試等待workQueue里的任務(wù)。

一旦超過keepAliveTime指定的時(shí)間,還獲取不到任務(wù),比如keepAliveTime是60秒,那么假如超過60秒獲取不到任務(wù),他就會(huì)自動(dòng)釋放掉了,這個(gè)線程就銷毀了。

整個(gè)過程,如下圖所示。

使用Java無界隊(duì)列的線程池會(huì)怎么樣

(4)無界隊(duì)列引發(fā)的內(nèi)存飆升

明白了線程池的運(yùn)行原理了,這個(gè)面試題就好解答了。

我們以最常用的fixed線程池舉例,他的線程池?cái)?shù)量是固定的,因?yàn)樗玫氖墙跤跓o界的LinkedBlockingQueue,幾乎可以無限制的放入任務(wù)到隊(duì)列里。

所以只要線程池里的線程數(shù)量達(dá)到了corePoolSize指定的數(shù)量之后,接下來就維持這個(gè)固定數(shù)量的線程了。

然后,所有任務(wù)都會(huì)入隊(duì)到workQueue里去,線程從workQueue獲取任務(wù)來處理。

這個(gè)隊(duì)列幾乎永遠(yuǎn)不會(huì)滿,當(dāng)然這是幾乎,因?yàn)長(zhǎng)inkedBlockingQueue默認(rèn)的最大任務(wù)數(shù)量是Integer.MAX_VALUE,非常大,近乎于可以理解為無限吧。

只要隊(duì)列不滿,就跟maximumPoolSize、keepAliveTime這些沒關(guān)系了,因?yàn)椴粫?huì)創(chuàng)建超過corePoolSize數(shù)量的線程的。

同樣,給大家來一張圖,我們來看看:

使用Java無界隊(duì)列的線程池會(huì)怎么樣

那么此時(shí)萬一每個(gè)線程獲取到一個(gè)任務(wù)之后,他處理的時(shí)間特別特別的長(zhǎng),長(zhǎng)到了令人發(fā)指的地步。比如處理一個(gè)任務(wù)要幾個(gè)小時(shí),此時(shí)會(huì)如何?

當(dāng)然會(huì)出現(xiàn)workQueue里不斷的積壓越來越多得任務(wù),不停的增加。

這個(gè)過程中會(huì)導(dǎo)致機(jī)器的內(nèi)存使用不停的飆升,最后也許極端情況下就導(dǎo)致JVM OOM了,系統(tǒng)就掛掉了。

所以這就是這個(gè)面試題背后你要知道的線程池的運(yùn)行原理,以及可能遇到的一些問題,大家要做到心里有數(shù)。

到此,相信大家對(duì)“使用Java無界隊(duì)列的線程池會(huì)怎么樣”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI