java線程池使用及原理面試題
引導(dǎo)語
線程池在日常面試中占比很大,主要是因?yàn)榫€程池內(nèi)容涉及的知識點(diǎn)較廣,比如涉及到隊(duì)列、線程、鎖等等,所以很多面試官喜歡把線程池作為問題的起點(diǎn),然后延伸到其它內(nèi)容,由于我們專欄已經(jīng)說過隊(duì)列、線程、鎖面試題了,所以本章面試題還是以線程池為主。
1、說說你對線程池的理解?
答:答題思路從大到小,從全面到局部,總的可以這么說,線程池結(jié)合了鎖、線程、隊(duì)列等元素,在請求量較大的環(huán)境下,可以多線程的處理請求,充分的利用了系統(tǒng)的資源,提高了處理請求的速度,細(xì)節(jié)可以從以下幾個(gè)方面闡述:
- ThreadPoolExecutor 類結(jié)構(gòu);
- ThreadPoolExecutor coreSize、maxSize 等重要屬性;
- Worker 的重要作用;
- submit 的整個(gè)過程。
通過以上總分的描述,應(yīng)該可以說清楚對線程池的理解了,如果是面對面面試的話,可以邊說邊畫出線程池的整體架構(gòu)圖(見《ThreadPoolExecutor 源碼解析》)。
2、ThreadPoolExecutor、Executor、ExecutorService、Runnable、Callable、FutureTask 之間的關(guān)系?
答:以上 6 個(gè)類可以分成兩大類:一種是定義任務(wù)類,一種是執(zhí)行任務(wù)類。
定義任務(wù)類:Runnable、Callable、FutureTask。Runnable 是定義無返回值的任務(wù),Callable 是定義有返回值的任務(wù),F(xiàn)utureTask 是對 Runnable 和 Callable 兩種任務(wù)的統(tǒng)一,并增加了對任務(wù)的管理功能;
執(zhí)行任務(wù)類:ThreadPoolExecutor、Executor、ExecutorService。Executor 定義最基本的運(yùn)行接口,ExecutorService 是對其功能的補(bǔ)充,ThreadPoolExecutor 提供真正可運(yùn)行的線程池類,三個(gè)類定義了任務(wù)的運(yùn)行機(jī)制。
日常的做法都是先根據(jù)定義任務(wù)類定義出任務(wù)來,然后丟給執(zhí)行任務(wù)類去執(zhí)行。
3、說一說隊(duì)列在線程池中起的作用?
答:作用如下:
當(dāng)請求數(shù)大于 coreSize 時(shí),可以讓任務(wù)在隊(duì)列中排隊(duì),讓線程池中的線程慢慢的消費(fèi)請求,實(shí)際工作中,實(shí)際線程數(shù)不可能等于請求數(shù),隊(duì)列提供了一種機(jī)制讓任務(wù)可排隊(duì),起一個(gè)緩沖區(qū)的作用;
當(dāng)線程消費(fèi)完所有的線程后,會阻塞的從隊(duì)列中拿數(shù)據(jù),通過隊(duì)列阻塞的功能,使線程不消亡,一旦隊(duì)列中有數(shù)據(jù)產(chǎn)生后,可立馬被消費(fèi)。
4、結(jié)合請求不斷增加時(shí),說一說線程池構(gòu)造器參數(shù)的含義和表現(xiàn)?
答:線程池構(gòu)造器各個(gè)參數(shù)的含義如下:
coreSize 核心線程數(shù);
maxSize 最大線程數(shù);
keepAliveTime 線程空閑的最大時(shí)間;
queue 有多種隊(duì)列可供選擇,比如:1:SynchronousQueue,為了避免任務(wù)被拒絕,要求線程池的 maxSize 無界,缺點(diǎn)是當(dāng)任務(wù)提交的速度超過消費(fèi)的速度時(shí),可能出現(xiàn)無限制的線程增長;2:LinkedBlockingQueue,無界隊(duì)列,未消費(fèi)的任務(wù)可以在隊(duì)列中等待;3:ArrayBlockingQueue,有界隊(duì)列,可以防止資源被耗盡;
線程新建的 ThreadFactory 可以自定義,也可以使用默認(rèn)的 DefaultThreadFactory,DefaultThreadFactory 創(chuàng)建線程時(shí),優(yōu)先級會被限制成 NORM_PRIORITY,默認(rèn)會被設(shè)置成非守護(hù)線程;
在 Executor 已經(jīng)關(guān)閉或?qū)ψ畲缶€程和最大隊(duì)列都使用飽和時(shí),可以使用 RejectedExecutionHandler 類進(jìn)行異常捕捉,有如下四種處理策略:ThreadPoolExecutor.AbortPolicy、ThreadPoolExecutor.DiscardPolicy、ThreadPoolExecutor.CallerRunsPolicy、ThreadPoolExecutor.DiscardOldestPolicy。
當(dāng)請求不斷增加時(shí),各個(gè)參數(shù)起的作用如下:
請求數(shù) < coreSize:創(chuàng)建新的線程來處理任務(wù);
coreSize <= 請求數(shù) && 能夠成功入隊(duì)列:任務(wù)進(jìn)入到隊(duì)列中等待被消費(fèi);
隊(duì)列已滿 && 請求數(shù) < maxSize:創(chuàng)建新的線程來處理任務(wù);
隊(duì)列已滿 && 請求數(shù) >= maxSize:使用 RejectedExecutionHandler 類拒絕請求。
5、coreSize 和 maxSize 可以動(dòng)態(tài)設(shè)置么,有沒有規(guī)則限制?
答:一般來說,coreSize 和 maxSize 在線程池初始化時(shí)就已經(jīng)設(shè)定了,但我們也可以通過 setCorePoolSize、setMaximumPoolSize 方法動(dòng)態(tài)的修改這兩個(gè)值。
setCorePoolSize 的限制見如下源碼:
// 如果新設(shè)置的值小于 coreSize,多余的線程在空閑時(shí)會被回收(不保證一定可以回收成功) // 如果大于 coseSize,會新創(chuàng)建線程 public void setCorePoolSize(int corePoolSize) { if (corePoolSize < 0) throw new IllegalArgumentException(); int delta = corePoolSize - this.corePoolSize; this.corePoolSize = corePoolSize; // 活動(dòng)的線程大于新設(shè)置的核心線程數(shù) if (workerCountOf(ctl.get()) > corePoolSize) // 嘗試將可以獲得鎖的 worker 中斷,只會循環(huán)一次 // 最后并不能保證活動(dòng)的線程數(shù)一定小于核心線程數(shù) interruptIdleWorkers(); // 設(shè)置的核心線程數(shù)大于原來的核心線程數(shù) else if (delta > 0) { // 并不清楚應(yīng)該新增多少線程,取新增核心線程數(shù)和等待隊(duì)列數(shù)據(jù)的最小值,夠用就好 int k = Math.min(delta, workQueue.size()); // 新增線程直到k,如果期間等待隊(duì)列空了也不會再新增 while (k-- > 0 && addWorker(null, true)) { if (workQueue.isEmpty()) break; } } }
setMaximumPoolSize 的限制見如下源碼:
// 如果 maxSize 大于原來的值,直接設(shè)置。 // 如果 maxSize 小于原來的值,嘗試干掉一些 worker public void setMaximumPoolSize(int maximumPoolSize) { if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize) throw new IllegalArgumentException(); this.maximumPoolSize = maximumPoolSize; if (workerCountOf(ctl.get()) > maximumPoolSize) interruptIdleWorkers(); }
6、說一說對于線程空閑回收的理解,源碼中如何體現(xiàn)的?
答:空閑線程回收的時(shí)機(jī):如果線程超過 keepAliveTime 時(shí)間后,還從阻塞隊(duì)列中拿不到任務(wù)(這種情況我們稱為線程空閑),當(dāng)前線程就會被回收,如果 allowCoreThreadTimeOut 設(shè)置成 true,core thread 也會被回收,直到還剩下一個(gè)線程為止,如果 allowCoreThreadTimeOut 設(shè)置成 false,只會回收非 core thread 的線程。
線程在任務(wù)執(zhí)行完成之后,之所有沒有消亡,是因?yàn)樽枞膹年?duì)列中拿任務(wù),在 keepAliveTime 時(shí)間后都沒有拿到任務(wù)的話,就會打斷阻塞,線程直接返回,線程的生命周期就結(jié)束了,JVM 會回收掉該線程對象,所以我們說的線程回收源碼體現(xiàn)就是讓線程不在隊(duì)列中阻塞,直接返回了,可以見 ThreadPoolExecutor 源碼解析章節(jié)第三小節(jié)的源碼解析。
7、如果我想在線程池任務(wù)執(zhí)行之前和之后,做一些資源清理的工作,可以么,如何做?
答:可以的,ThreadPoolExecutor 提供了一些鉤子函數(shù),我們只需要繼承 ThreadPoolExecutor 并實(shí)現(xiàn)這些鉤子函數(shù)即可。在線程池任務(wù)執(zhí)行之前實(shí)現(xiàn) beforeExecute 方法,執(zhí)行之后實(shí)現(xiàn) afterExecute 方法。
8、線程池中的線程創(chuàng)建,拒絕請求可以自定義實(shí)現(xiàn)么?如何自定義?
答:可以自定義的,線程創(chuàng)建默認(rèn)使用的是 DefaultThreadFactory,自定義話的只需要實(shí)現(xiàn) ThreadFactory 接口即可;拒絕請求也是可以自定義的,實(shí)現(xiàn) RejectedExecutionHandler 接口即可;在 ThreadPoolExecutor 初始化時(shí),將兩個(gè)自定義類作為構(gòu)造器的入?yún)鬟f給 ThreadPoolExecutor 即可。
9、說說你對 Worker 的理解?
答:詳見《ThreadPoolExecutor 源碼解析》中 1.4 小節(jié)。
10、說一說 submit 方法執(zhí)行的過程?
答:詳見《ThreadPoolExecutor 源碼解析》中 2 小節(jié)。
11、說一說線程執(zhí)行任務(wù)之后,都在干啥?
答:線程執(zhí)行任務(wù)完成之后,有兩種結(jié)果:
線程會阻塞從隊(duì)列中拿任務(wù),沒有任務(wù)的話無限阻塞;線程會阻塞從隊(duì)列中拿任務(wù),沒有任務(wù)的話阻塞一段時(shí)間后,線程返回,被 JVM 回收。
12、keepAliveTime 設(shè)置成負(fù)數(shù)或者是 0,表示無限阻塞?
答:這種是不對的,如果 keepAliveTime 設(shè)置成負(fù)數(shù),在線程池初始化時(shí),就會直接報(bào) IllegalArgumentException 的異常,而設(shè)置成 0,隊(duì)列如果是 LinkedBlockingQueue 的話,執(zhí)行 workQueue.poll (keepAliveTime, TimeUnit.NANOSECONDS) 方法時(shí),如果隊(duì)列中沒有任務(wù),會直接返回 null,導(dǎo)致線程立馬返回,不會無限阻塞。
如果想無限阻塞的話,可以把 keepAliveTime 設(shè)置的很大,把 TimeUnit 也設(shè)置的很大,接近于無限阻塞。
13、說一說 Future.get 方法是如何拿到線程的執(zhí)行結(jié)果的?
答:我們需要明確幾點(diǎn):
submit 方法的返回結(jié)果實(shí)際上是 FutureTask,我們平時(shí)都是針對接口編程,所以使用的是 Future.get 來拿到線程的執(zhí)行結(jié)果,實(shí)際上是 FutureTask.get ,其方法底層是從 FutureTask 的 outcome 屬性拿值的;《ThreadPoolExecutor 源碼解析》中 2 小節(jié)中詳細(xì)說明了 submit 方法最終會把線程的執(zhí)行結(jié)果賦值給 outcome。
結(jié)合 1、2,當(dāng)線程執(zhí)行完成之后,自然就可以從 FutureTask 的 outcome 屬性中拿到值。
14、總結(jié)
如果我們弄清楚 ThreadPoolExecutor 的原理之后,線程池的面試題都很簡單,所以建議大家多看看 《ThreadPoolExecutor 源碼解析》這小節(jié)。
以上就是java線程池使用及原理面試題的詳細(xì)內(nèi)容,更多關(guān)于java線程池面試題的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot配置多數(shù)據(jù)源的一款框架(dynamic-datasource-spring-boot-starter
dynamic-datasource-spring-boot-starter 是一個(gè)基于 springboot 的快速集成多數(shù)據(jù)源的啟動(dòng)器,今天通過本文給大家分享這款框架配置springboot多數(shù)據(jù)源的方法,一起看看吧2021-09-09解析SpringBoot?搭建基于?MinIO?的高性能存儲服務(wù)的問題
Minio是Apache?License?v2.0下發(fā)布的對象存儲服務(wù)器,使用MinIO構(gòu)建用于機(jī)器學(xué)習(xí),分析和應(yīng)用程序數(shù)據(jù)工作負(fù)載的高性能基礎(chǔ)架構(gòu)。這篇文章主要介紹了SpringBoot?搭建基于?MinIO?的高性能存儲服務(wù),需要的朋友可以參考下2022-03-03Java實(shí)現(xiàn)UDP通信過程實(shí)例分析【服務(wù)器端與客戶端】
這篇文章主要介紹了Java實(shí)現(xiàn)UDP通信過程,結(jié)合實(shí)例形式分析了java實(shí)現(xiàn)UDP服務(wù)器端與客戶端相關(guān)操作技巧與注意事項(xiàng),需要的朋友可以參考下2020-05-05java?Semaphore共享鎖實(shí)現(xiàn)原理解析
這篇文章主要為大家介紹了Semaphore共享鎖實(shí)現(xiàn)原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01解決使用redisTemplate高并發(fā)下連接池滿的問題
這篇文章主要介紹了解決使用redisTemplate高并發(fā)下連接池滿的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12