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