線程池的幾個面試重要考點(diǎn)
阿粉有點(diǎn)驚嘆最近的面試題,因?yàn)閺闹暗幕A(chǔ)的面試題,到之后的一些涉及到分布式和微服務(wù)的面試題,再到現(xiàn)在的線程池的一些面試題,反正不同的面試官,就有不同的針對方向,可能現(xiàn)在的面試官比較想考驗(yàn)?zāi)愕亩喾矫娴哪芰Π?,而最近,一個讀者就反饋給了阿粉說,面試官全程就從線程這塊入手,整的自己有點(diǎn)尷尬,但是好在有驚無險的入職了,我們來看看面試官都問了什么內(nèi)容?
進(jìn)程和線程的概念,你能說一下自己的理解么?這個問題,有點(diǎn)基礎(chǔ),不過肯定是之后的開胃小菜。
進(jìn)程和線程的關(guān)系
進(jìn)程就是應(yīng)用程序在內(nèi)存中分配的空間,也就是正在運(yùn)行的程序,各個進(jìn)程之間互不干擾。同時進(jìn)程保存著程序每一個時刻運(yùn)行的狀態(tài)。
讓一個線程執(zhí)行一個子任務(wù),這樣一個進(jìn)程就包含了多個線程,每個線程負(fù)責(zé)一個單獨(dú)的子任務(wù)。
進(jìn)程是一個獨(dú)立的運(yùn)行環(huán)境,而線程是在進(jìn)程中執(zhí)行的一個任務(wù)。他們兩個本質(zhì)的區(qū)別是是否單獨(dú)占有內(nèi)存地址空間及其它系統(tǒng)資源(比如I/O)
總得來說就是,線程是屬于進(jìn)程中的一個任務(wù),應(yīng)該算是包含的關(guān)系。
進(jìn)程是操作系統(tǒng)進(jìn)行資源分配的基本單位,而線程是操作系統(tǒng)進(jìn)行調(diào)度的基本單位。
多進(jìn)程的方式也可以實(shí)現(xiàn)并發(fā),為什么我們要使用多線程?這個問題就有意思了,你如果不是很了解的話,這個問題還真不好回答。
多進(jìn)程方式確實(shí)可以實(shí)現(xiàn)并發(fā),但使用多線程,是比多進(jìn)程有好處的。
1.進(jìn)程間的通信比較復(fù)雜,而線程間的通信比較簡單,通常情況下,我們需要使用共享資源,這些資源在線程間的通信比較容易。
2.進(jìn)程是重量級的,而線程是輕量級的,故多線程方式的系統(tǒng)開銷更小。
資源浪費(fèi)屬于一方面的有點(diǎn),通信簡單也是另外一方面的優(yōu)點(diǎn),就憑借這兩點(diǎn)的內(nèi)容,還能選擇多進(jìn)程?
線程池的內(nèi)容
你在工作中使用過線程池么?為什么使用線程池?這個問題有點(diǎn)尷尬,為什么這么說?
如果你說你沒用過,那你這在面試官這里就相當(dāng)于只寫 CRUD 的邏輯業(yè)務(wù)了,也不整點(diǎn)其他的內(nèi)容。
如果你說你用過,你就得回答接下來的一系列關(guān)于線程池的問題了。這個阿粉還是推薦,實(shí)話實(shí)話,就算你沒用過,那么也別瞎扯,不然你這給自己挖的坑,肯定自己得跳下去。
那么我們就從為什么使用線程池來入手分析唄。
首先我們就要思考一件事,不使用線程池的話,創(chuàng)建線程有什么弊端么?
在java中,如果每個請求到達(dá)就創(chuàng)建一個新線程,那對服務(wù)器的資源消耗是不是有點(diǎn)大,創(chuàng)建線程,銷毀線程,創(chuàng)建線程,銷毀線程,然后再各種線程之間來回的切換,這一來一回,是不是感覺資源浪費(fèi)就體現(xiàn)出來了。
那么線程池會避免這個情況么?
這就出來了優(yōu)點(diǎn)1了
創(chuàng)建/銷毀線程需要消耗系統(tǒng)資源,線程池可以復(fù)用已創(chuàng)建的線程。
雖然這個優(yōu)點(diǎn)很明確,但是還不是主要原因,主要原因如下:
控制并發(fā)的數(shù)量。并發(fā)數(shù)量過多,可能會導(dǎo)致資源消耗過多,從而造成服務(wù)器崩潰。(主要原因)
可以對線程做統(tǒng)一管理
分析一下線程池的原理 Java中的線程池頂層接口是Executor接口,但是使用的肯定不是這個,是 ThreadPoolExecutor
我們看看 ThreadPoolExecutor 構(gòu)造函數(shù)
竟然參數(shù)這么多,分別都代表什么意思呢?
int corePoolSize:該線程池中核心線程數(shù)最大值。
int maximumPoolSize:該線程池中線程總數(shù)最大值。
long keepAliveTime:非核心線程閑置超時時長。
TimeUnit unit:keepAliveTime的單位。
BlockingQueue workQueue:阻塞隊列,維護(hù)著等待執(zhí)行的Runnable任務(wù)對象。
corePoolSize核心線程最大值:這個值怎么確定?
一般這個問題是相對來說比較棘手的,如果面試官問這個問題,那一般的同學(xué)肯定頭大,我知道啥意思,但是這個怎么設(shè)置,我怎么定義呢?
其實(shí)有個計算公式:
線程等待時間所占比例越高,需要越多線程。線程CPU時間所占比例越高,需要越少線程
maximumPoolSize :線程池中線程總數(shù)最大值
這個值實(shí)際上就是 核心線程數(shù) + 非核心線程數(shù)量
keepAliveTime: 這個值如果設(shè)定了,那么非核心線程如果處于閑置狀態(tài)超過該值,就會被銷毀。
BlockingQueue:阻塞隊列
看樣子感覺像 MQ 里面的東西,想到隊列,我們就又能聯(lián)想到生產(chǎn)者和消費(fèi)者,這時候就出現(xiàn)了個問題,為什么要有阻塞隊列呢?
是不是就出現(xiàn)了消費(fèi)者模式,生產(chǎn)者一直生產(chǎn)資源,消費(fèi)者一直消費(fèi)資源,資源存儲在一個緩沖池中。
我們在實(shí)現(xiàn)這個模式的時候,多個線程操作共享變量,于是就帶來了線程安全性的問題,造成重復(fù)消費(fèi)和死鎖,這時候阻塞隊列就出現(xiàn)了,當(dāng)緩沖池空了,我們需要阻塞消費(fèi)者,喚醒生產(chǎn)者;當(dāng)緩沖池滿了,我們需要阻塞生產(chǎn)者,喚醒消費(fèi)者。
而BlockingQueue提供了線程安全的隊列訪問方式,并發(fā)包下很多高級同步類的實(shí)現(xiàn)都是基于BlockingQueue實(shí)現(xiàn)的。
也就是說,你就只負(fù)責(zé)生產(chǎn)和消費(fèi),安全問題,JDK 來給你保證。
說到這里,我們不在繼續(xù)往下延伸了,等下次阿粉直接在吧 BlockingQueue 完全的分析一波,應(yīng)為 BlockingQueue 絕對得需要一個長篇的內(nèi)容才能解釋清楚。
分析完里面的參數(shù),這時候,就得來看看線程池是怎么處理線程任務(wù)的,不然那怎么和面試官battle。
線程池是如何處理內(nèi)部的線程任務(wù)的
在 execute 方法中,ctl.get()是獲取線程池狀態(tài)。
流程如下:
1,首先線程池判斷基本線程池是否已滿,沒滿,創(chuàng)建一個工作線程來執(zhí)行任務(wù)。滿了,則進(jìn)入下個流程。
2,其次線程池判斷工作隊列是否已滿?沒滿,則將新提交的任務(wù)存儲在工作隊列里。滿了,則進(jìn)入下個流程。
3,最后線程池判斷整個線程池是否已滿,沒滿,則創(chuàng)建一個新的工作線程來執(zhí)行任務(wù),滿了,則交給飽和策略來處理這個任務(wù)。
如果你能把這些在面試的時候說清楚,那么至少在線程池這個知識點(diǎn)上,你是沒有任何問題了,這樣就可以愉快并且開心的走下一個知識點(diǎn)了。