你好,我是yes。java
這兩面試題是基友朋友最近去面滴滴遇到的,今天就藉着這兩面試真題來深刻一波線程池吧,這篇文章力求把線程池核心點和常問的面試點一網打盡,固然我的能力有限,可能會有遺漏,歡迎留言補充!面試
先把問題列出來,若是你都答得出來,那不必看下去:算法
-
爲何會有線程池?緩存
-
簡單手寫一個線程池?安全
-
爲何要把任務先放在任務隊列裏面,而不是把線程先拉滿到最大線程數?微信
-
線程池如何動態修改核心線程數和最大線程數?多線程
-
若是你是 JDK 設計者,如何設計?併發
-
若是要讓你設計一個線程池,你要怎麼設計?ide
-
你是如何理解核心線程的?函數
-
你是怎麼理解 KeepAliveTime 的?
-
那 workQueue 有什麼用?
-
你是如何理解拒絕策略的?
-
你說你看過源碼,那你確定知道線程池裏的 ctl 是幹嗎的咯?
-
你知道線程池有幾種狀態嗎?
-
你知道線程池的狀態是如何變遷的嗎?
-
如何修改原生線程池,使得能夠先拉滿線程數再入任務隊列排隊?
-
Tomcat 中的定製化線程池實現 若是線程池中的線程在執行任務的時候,拋異常了,會怎麼樣?
-
原生線程池的核心線程必定伴隨着任務慢慢建立的嗎?
-
線程池的核心線程在空閒的時候必定不會被回收嗎?
接得住嗎?話很少說,發車!
爲何會有線程池?
想要深刻理解線程池的原理得先知道爲何須要線程池。
首先你要明白,線程是一個重資源,JVM 中的線程與操做系統的線程是一對一的關係,因此在 JVM 中每建立一個線程就須要調用操做系統提供的 API 建立線程,賦予資源,而且銷燬線程一樣也須要系統調用。
而系統調用就意味着上下文切換等開銷,而且線程也是須要佔用內存的,而內存也是珍貴的資源。
所以線程的建立和銷燬是一個重操做,而且線程自己也佔用資源。
而後你還須要知道,線程數並非越多越好。
咱們都知道線程是 CPU 調度的最小單位,在單核時代,若是是純運算的操做是不須要多線程的,一個線程一直執行運算便可。但若是這個線程正在等待 I/O 操做,此時 CPU 就處於空閒狀態,這就浪費了 CPU 的算力,所以有了多線程,在某線程等待 I/O 等操做的時候,另外一個線程頂上,充分利用 CPU,提升處理效率。
此時的多線程主要是爲了提升 CPU 的利用率而提出。
而隨着 CPU 的發展,核心數愈來愈多,能同時運行的線程數也提高了,此時的多線程不只是爲了提升單核 CPU 的利用率,也是爲了充分利用多個核心。
至此想必應該明白了爲何會有多線程,無非就是爲了充分利用 CPU 空閒的時間,一刻也不想讓他停下來。
但 CPU 的核心數有限,同時能運行的線程數有限,因此須要根據調度算法切換執行的線程,而線程的切換須要開銷,好比替換寄存器的內容、高速緩存的失效等等。
若是線程數太多,切換的頻率就變高,可能使得多線程帶來的好處抵不過線程切換帶來的開銷,得不償失。
所以線程的數量須要得以控制,結合上述的描述可知,線程的數量與 CPU 核心數和 I/O 等待時長息息相關。
小結一下:
-
Java中線程與操做系統線程是一比一的關係。
-
線程的建立和銷燬是一個「較重」的操做。
-
多線程的主要是爲了提升 CPU 的利用率。
-
線程的切換有開銷,線程數的多少須要結合 CPU核心數與 I/O 等待佔比。
綜上咱們知道了線程的這些特性,因此說它不是一個能夠「隨意拿捏」的東西,咱們須要重視它,好好規劃和管理它,充分利用硬件的能力,從而提高程序執行效率,因此線程池應運而生。
什麼是線程池?
那咱們要如何管理好線程呢?
由於線程數太少沒法充分利用 CPU ,太多的話因爲上下文切換的消耗又得不償失,因此咱們須要評估系統所要承載的併發量和所執行任務的特性,得出大體須要多少個線程數才能充分利用 CPU,所以須要控制線程數量。
又由於線程的建立和銷燬是一個「重」操做,因此咱們須要避免線程頻繁地建立與銷燬,所以咱們須要緩存一批線程,讓它們時刻準備着執行任務。
目標已經很清晰了,弄一個池子,裏面存放約定數量的線程,這就是線程池,一種池化技術。
熟悉對象池、鏈接池的朋友確定對池化技術不陌生,通常池化技術的使用方式是從池子裏拿出資源,而後使用,用完了以後歸還。
可是線程池的實現不太同樣,不是說咱們從線程池裏面拿一個線程來執行任務,等任務執行完了以後再歸還線程,你能夠想一下這樣作是否合理。
線程池的常見實現更像是一個黑盒存在,咱們設置好線程池的大小以後,直接往線程池裏面丟任務,而後就無論了。
剝開來看,線程池實際上是一個典型的生產者-消費者模式。
線程池內部會有一個隊列來存儲咱們提交的任務,而內部線程不斷地從隊列中索取任務來執行,這就是線程池最原始的執行機制。
按照這個思路,咱們能夠很容易的實現一個簡單版線程池,想必看了下面這個代碼實現,對線程池的核心原理就會了然於心。
首先線程池內須要定義兩個成員變量,分別是阻塞隊列和線程列表,而後自定義線程使它的任務就是不斷的從阻塞隊列中拿任務而後執行。
`@Slf4j
public class YesThreadPool {
BlockingQueue<Runnable> taskQueue; //存聽任務的阻塞隊列
List<YesThread> threads; //線程列表
YesThreadPool(BlockingQueue<Runnable> taskQueue, int threadSize) {
this.taskQueue = taskQueue;
threads = new ArrayList<>(threadSize);
// 初始化線程,並定義名稱
IntStream.rangeClosed(1, threadSize).forEach((i)-> {
YesThread thread = new YesThread("yes-task-thread-" + i);
thread.start();
threads.add(thread);
});
}
//提交任務只是往任務隊列裏面塞任務
public void execute(Runnable task) throws InterruptedException {
taskQueue.put(task);
}
class YesThread extends Thread { //自定義一個線程
public YesThread(String name) {
super(name);
}
@Override
public void run() {
while (true) { //死循環
Runnable task = null;
try {
task = taskQueue.take(); //不斷從任務隊列獲取任務
} catch (InterruptedException e) {
logger.error("記錄點東西.....", e);
}
task.run(); //執行
}
}
}
}
`
一個簡單版線程池就完成了,簡單吧!
再寫個 main 方法用一用,絲滑,很是絲滑。
public static void main(String[] args) { YesThreadPool pool = new YesThreadPool(new LinkedBlockingQueue<>(10), 3); IntStream.rangeClosed(1, 5).forEach((i)-> { try { pool.execute(()-> { System.out.println(Thread.currentThread().getName() + " 公衆號:yes的練級攻略"); }); } catch (InterruptedException e) { logger.error("記錄點東西.....", e); } }); }
運行結果以下:
下次面試官讓你手寫線程池,直接上這個簡單版,而後他會開始讓你優化,好比什麼線程一開始都 start 了很差,想懶加載,而後xxxx...最終其實就是想往李老爺實現的 ThreadPoolExecutor 上面靠。
那就來嘛。
ThreadPoolExecutor 剖析
這玩意就是常被問的線程池的實現類了,先來看下構造函數:
核心原理其實和我們上面實現的差很少,只是生產級別的那確定是要考慮的更多,接下來咱們就來看看此線程池的工做原理。
先來一張圖:
簡單來講線程池把任務的提交和任務的執行剝離開來,當一個任務被提交到線程池以後:
-
若是此時線程數 小於核心線程數,那麼就會新起一個線程來執行當前的任務。
-
若是此時線程數 大於核心線程數,那麼就會將任務塞入阻塞隊列中,等待被執行。
-
若是阻塞隊列滿了,而且此時線程數 小於最大線程數,那麼會建立新線程來執行當前任務。
-
若是阻塞隊列滿了,而且此時線程數 大於最大線程數,那麼會採起拒絕策略。
以上就是任務提交給線程池後各類情況彙總,一個很容易出現理解錯誤的地方就是當線程數達到核心數的時候,任務是先入隊,而不是先建立最大線程數。
從上述可知,線程池裏的線程不是一開始就直接拉滿的,是根據任務量開始慢慢增多的,這就算一種懶加載,到用的時候再建立線程,節省資源。
來先吃我幾問。
此時線程數小於核心線程數,而且線程都處於空閒狀態,現提交一個任務,是新起一個線程仍是給以前建立的線程?
李總是這樣說的:If fewer than corePoolSize threads are running, try to start a new thread with the given command as its first task.
我以爲把 threads are running
去了,更合理一些,此時線程池會新起一個線程來執行這個新任務,無論老線程是否空閒。
你是如何理解核心線程的 ?
從上一個問題能夠看出,線程池雖然說默認是懶建立線程,可是它實際是想要快速擁有核心線程數的線程。核心線程指的是線程池承載平常任務的中堅力量,也就是說本質上線程池是須要這麼些數量的線程來處理任務的,因此在懶中又急着建立它。
而最大線程數實際上是爲了應付突發情況。
舉個裝修的例子,正常狀況下施工隊只要 5 我的去幹活,這 5 人其實就是核心線程,可是因爲工頭接的活太多了,致使 5 我的在約定工期內幹不完,因此工頭又去找了 2 我的來一塊兒幹,因此 5 是核心線程數,7 是最大線程數。
平時就是 5 我的幹活,特別忙的時候就找 7 個,等閒下來就會把多餘的 2 個辭了。
看到這裏你可能會以爲核心線程在線程池裏面會有特殊標記?
並無,不管是核心仍是非核心線程,在線程池裏面都是一視同仁,當淘汰的時候不會管是哪些線程,反正留下核心線程數個線程便可,下文會做詳解。
你是怎麼理解 KeepAliveTime 的?
這就是上面提到的,線程池其實想要的只是核心線程數個線程,可是又預留了一些數量來預防突發情況,當突發情況過去以後,線程池但願只維持核心線程數的線程,因此就弄了個 KeepAliveTime,當線程數大於核心數以後,若是線程空閒了一段時間(KeepAliveTime),就回收線程,直到數量與核心數持平。
那 workQueue 有什麼用?
緩存任務供線程獲取,這裏要注意限制工做隊列的大小。隊列長了,堆積的任務就多,堆積的任務多,後面任務等待的時長就長。
想一想你點擊一個按鈕是一直轉圈等半天沒反應舒服,仍是直接報錯舒服,因此有時心是好的,想盡可能完成提交的任務,可是用戶體驗不如直接拒絕。更有可能因爲容許囤積的任務過多,致使資源耗盡而系統崩潰。
因此工做隊列起到一個緩衝做用,具體隊列長度須要結合線程數,任務的執行時長,能承受的等待時間等。
你是如何理解拒絕策略的?
線程數總有拉滿的一天,工做隊列也是同樣,若是二者都滿了,此時的提交任務就須要拒絕,默認實現是 AbortPolicy 直接拋出異常。
剩下的拒絕策略有直接丟棄任務一聲不吭的、讓提交任務的線程本身運行的、淘汰老的未執行的任務而空出位置的,具體用哪一個策略,根據場景選擇。固然也能夠自定義拒絕策略,實現 RejectedExecutionHandler
這個接口便可。
因此線程池儘量只維護核心數量的線程,提供任務隊列暫存任務,並提供拒絕策略來應對過載的任務。
這裏還有個細節,若是線程數已經達到核心線程數,那麼新增長的任務只會往任務隊列裏面塞,不會直接給予某個線程,若是任務隊列也滿了,新增最大線程數的線程時,任務是能夠直接給予新建的線程執行的,而不是入隊。
感受已經會了?那再來看幾道面試題:
你說你看過源碼,那你確定知道線程池裏的 ctl 是幹嗎的咯?
其實看下注釋就很清楚了,ctl 是一個涵蓋了兩個概念的原子整數類,它將工做線程數和線程池狀態結合在一塊兒維護,低 29 位存放 workerCount,高 3 位存放 runState。
其實併發包中有不少實現都是一個字段存多個值的,好比讀寫鎖的高 16 位存放讀鎖,低 16 位存放寫鎖,這種一個字段存放多個值能夠更容易的維護多個值之間的一致性,也算是極簡主義。
你知道線程池有幾種狀態嗎?
註解說的很明白,我再翻譯一下:
-
RUNNING:能接受新任務,並處理阻塞隊列中的任務
-
SHUTDOWN:不接受新任務,可是能夠處理阻塞隊列中的任務
-
STOP:不接受新任務,而且不處理阻塞隊列中的任務,而且還打斷正在運行任務的線程,就是直接撂擔子不幹了!
-
TIDYING:全部任務都終止,而且工做線程也爲0,處於關閉以前的狀態
-
TERMINATED:已關閉。
你知道線程池的狀態是如何變遷的嗎?
註釋裏面也寫的很清楚,我再畫個圖
爲何要把任務先放在任務隊列裏面,而不是把線程先拉滿到最大線程數?
我說下個人我的理解。
其實通過上面的分析能夠得知,線程池本意只是讓核心數量的線程工做着,不管是 core 的取名,仍是 keepalive 的設定,因此你能夠直接把 core 的數量設爲你想要線程池工做的線程數,而任務隊列起到一個緩衝的做用。最大線程數這個參數更像是無奈之舉,在最壞的狀況下作最後的努力,去新建線程去幫助消化任務。
因此我我的以爲沒有爲何,就是這樣設計的,而且這樣的設定挺合理。
固然若是你想要扯一扯 CPU 密集和 I/O 密集,那能夠扯一扯。
原生版線程池的實現能夠認爲是偏向 CPU 密集的,也就是當任務過多的時候不是先去建立更多的線程,而是先緩存任務,讓核心線程去消化,從上面的分析咱們能夠知道,當處理 CPU 密集型任務的時,線程太多反而會因爲線程頻繁切換的開銷而得不償失,因此優先堆積任務而不是建立新的線程。
而像 Tomcat 這種業務場景,大部分狀況下是須要大量 I/O 處理的狀況就作了一些定製,修改了原生線程池的實現,使得在隊列沒滿的時候,能夠建立線程至最大線程數。
如何修改原生線程池,使得能夠先拉滿線程數再入任務隊列排隊?
若是瞭解線程池的原理,很輕鬆的就知道關鍵點在哪,就是隊列的 offer 方法。
execute 方法想必你們都不陌生,就是給線程池提交任務的方法。在這個方法中能夠看到只要在 offer 方法內部判斷此時線程數還小於最大線程數的時候返回 false,便可走下面 else if
中 addWorker
(新增線程)的邏輯,若是數量已經達到最大線程數,直接入隊便可。
詳細的咱們能夠看看 Tomcat 中是如何定製線程的。
Tomcat 中的定製化線程池實現
public class ThreadPoolExecutor extends java.util.concurrent.ThreadPoolExecutor {}
能夠看到先繼承了 JUC 的線程池,而後咱們重點關注一下 execute 這個方法
這裏能夠看到,Tomcat 維護了一個 submittedCount 變量,這個變量的含義是統計已經提交的可是還未完成的任務數量(記住這個變量,很關鍵),因此只要提交一個任務,這個數就加一,而且捕獲了拒絕異常,再次嘗試將任務入隊,這個操做實際上是爲了儘量的挽救回一些任務,由於這麼點時間差可能已經執行完不少任務,隊列騰出了空位,這樣就不須要丟棄任務。
而後咱們再來看下代碼裏出現的 TaskQueue,這個就是上面提到的定製關鍵點了。
public class TaskQueue extends LinkedBlockingQueue<Runnable> { private transient volatile ThreadPoolExecutor parent = null; ........ }
能夠看到這個任務隊列繼承了 LinkedBlockingQueue,而且有個 ThreadPoolExecutor 類型的成員變量 parent ,咱們再來看下 offer 方法的實現,這裏就是修改原來線程池任務提交與線程建立邏輯的核心了。
從上面的邏輯能夠看出是有機會在隊列還未滿的時候,先建立線程至最大線程數的!
再補充一下,若是對直接返回 false 就能建立線程感到疑惑的話,往上翻一翻,上面貼了原生線程池 execute 的邏輯。
而後上面的代碼其實只看到 submittedCount 的增長,正常的減小在 afterExecute
裏實現了。
而這個 afterExecute
在任務執行完畢以後就會調用,與之對應的還有個 beforeExecute
,在任務執行以前調用。
至此,想必 Tomcat 中的定製化線程池的邏輯已經明白了。
若是線程池中的線程在執行任務的時候,拋異常了,會怎麼樣?
嘿嘿,細心的同窗想必已經瞄到了上面的代碼,task.run() 被 try catch finally
包裹,異常被扔到了 afterExecute
中,而且也繼續被拋了出來。
而這一層外面,還有個try finally
,因此異常的拋出打破了 while 循環,最終會執行 `processWorkerExit方法
咱們來看下這個方法,其實邏輯很簡單,把這個線程廢了,而後新建一個線程替換之。
移除了引用等於銷燬了,這事兒 GC 會作的。
因此若是一個任務執行一半就拋出異常,而且你沒有自行處理這個異常,那麼這個任務就這樣戛然而止了,後面也不會有線程繼續執行剩下的邏輯,因此要自行捕獲和處理業務異常。
addWorker 的邏輯就不分析了,就是新建一個線程,而後塞到 workers 裏面,而後調用 start()
讓它跑起來。
原生線程池的核心線程必定伴隨着任務慢慢建立的嗎?
並非,線程池提供了兩個方法:
-
prestartCoreThread:啓動一個核心線程
-
prestartAllCoreThreads :啓動全部核心線程
不要小看這個預建立方法,預熱很重要,否則剛重啓的一些服務有時是頂不住瞬時請求的,就立馬崩了,因此有預熱線程、緩存等等操做。
線程池的核心線程在空閒的時候必定不會被回收嗎?
有個 allowCoreThreadTimeOut 方法,把它設置爲 true ,則全部線程都會超時,不會有核心數那條線的存在。
具體是會調用 interruptIdleWorkers
這個方法。
這裏須要講一下的是 w.tryLock() 這個方法,有些人可能會奇怪,Worker 怎麼還能 lock。
Worker 是屬於工做線程的封裝類,它不只實現了 Runnable 接口,還繼承了 AQS。
之因此要繼承 AQS 就是爲了用上 lock 的狀態,執行任務的時候上鎖,任務執行完了以後解鎖,這樣執行關閉線程池等操做的時候能夠經過 tryLock 來判斷此時線程是否在幹活,若是 tryLock 成功說明此時線程是空閒的,能夠安全的回收。
與interruptIdleWorkers
對應的還有一個 interruptWorkers
方法,從名字就能看出差異,不空閒的 worker 也直接給打斷了。
根據這兩個方法,又能夠扯到 shutdown 和 shutdownNow,就是關閉線程池的方法,一個是安全的關閉線程池,會等待任務都執行完畢,一個是粗暴的直接咔嚓了全部線程,管你在不在運行,兩個方法分別調用的就是 interruptIdleWorkers() 和 interruptWorkers() 來中斷線程。
這又能夠引伸出一個問題,shutdownNow 了以後還在任務隊列中的任務咋辦?眼尖的小夥伴應該已經看到了,線程池還算負責,把未執行的任務拖拽到了一個列表中而後返回,至於怎麼處理,就交給調用者了!
詳細就是上面的 drainQueue 方法。
這裏可能又會有同窗有疑問,都 drainTo 了,爲何還要判斷一下隊列是否爲空,而後進行循環?
那是由於若是隊列是 DelayQueue 或任何其餘類型的隊列,其中 poll 或 drainTo 可能沒法刪除某些元素,因此須要遍歷,逐個刪除它們。
回到最開始的面試題
線程池如何動態修改核心線程數和最大線程數?
其實之因此會有這樣的需求是由於線程數是真的很差配置。
你可能會在網上或者書上看到不少配置公式,好比:
-
CPU 密集型的話,核心線程數設置爲 CPU核數+1
-
I/O 密集型的話,核心線程數設置爲 2*CPU核數
好比:
線程數=CPU核數 *(1+線程等待時間 / 線程時間運行時間)
這個比上面的更貼合與業務,還有一些理想的公式就不列了。就這個公式而言,這個線程等待時間就很難測,拿 Tomcat 線程池爲例,每一個請求的等待時間能知道?不一樣的請求不一樣的業務,就算相同的業務,不一樣的用戶數據量也不一樣,等待時間也不一樣。
因此說線程數真的很難經過一個公式一勞永逸,線程數的設定是一個迭代的過程,須要壓測適時調整,以上的公式作個初始值開始調試是 ok 的。
再者,流量的突發性也是沒法判斷的,舉個例子 1 秒內一共有 1000 個請求量,可是若是這 1000 個請求量都是在第一毫秒內瞬時進來的呢?
這就很須要線程池的動態性,也是這個上面這個面試題的需求來源。
原生的線程池核心咱們大體都過了一遍,不過這幾個方法一直沒提到,先來看看這幾個方法:
我就不一一翻譯了,大體能夠看出線程池其實已經給予方法暴露出內部的一些狀態,例如正在執行的線程數、已完成的任務數、隊列中的任務數等等。
固然你能夠想要更多的數據監控都簡單的,像 Tomcat 那種繼承線程池以後本身加唄,動態調整的第一步監控就這樣搞定了!定時拉取這些數據,而後搞個看板,再結合郵件、短信、釘釘等報警方式,咱們能夠很容易的監控線程池的狀態!
接着就是動態修改線程池配置了。
能夠看到線程池已經提供了諸多修改方法來更改線程池的配置,因此李老都已經考慮到啦!
一樣,也能夠繼承線程池增長一些方法來修改,看具體的業務場景了。一樣搞個頁面,而後給予負責人員配置修改便可。
因此原生線程池已經提供修改配置的方法,也對外暴露出線程池內部執行狀況,因此只要咱們實時監控狀況,調用對應的 set 方法,便可動態修改線程池對應配置。
回答面試題的時候必定要提監控,顯得你是有的放矢的。
若是你是 JDK 設計者,如何設計?
其實我以爲這個是緊接着上一題問的,應該算是同一個問題。
並且 JDK 設計者已經設計好了呀?因此怎麼說我也不清楚,不過咱們能夠說一說具體的實現邏輯唄。
先來看下設置核心線程數的方法:
隨着註釋看下來應該沒什麼問題,就是那個 k 值我說一下,李老以爲核心線程數是配置了,可是此時線程池內部是否須要這麼多線程是不肯定的,那麼就按工做隊列裏面的任務數來,直接按任務數馬上新增線程,當任務隊列爲空了以後就終止新增。
這其實和李老設計的默認核心線程數增長策略是一致的,都是懶建立線程。
再看看設置最大線程數的方法:
沒啥花頭,調用的 interruptIdleWorkers
以前都分析過了。
我再貼一下以前寫過的線程池設計面試題吧。
若是要讓你設計一個線程池,你要怎麼設計?
這種設計類問題仍是同樣,先說下理解,代表你是知道這個東西的用處和原理的,而後開始 BB。基本上就是按照現有的設計來講,再添加一些我的看法。
線程池講白了就是存儲線程的一個容器,池內保存以前創建過的線程來重複執行任務,減小建立和銷燬線程的開銷,提升任務的響應速度,並便於線程的管理。
我我的以爲若是要設計一個線程池的話得考慮池內工做線程的管理、任務編排執行、線程池超負荷處理方案、監控。
初始化線程數、核心線程數、最大線程池都暴露出來可配置,包括超過核心線程數的線程空閒消亡配置。
任務的存儲結構可配置,能夠是無界隊列也能夠是有界隊列,也能夠根據配置分多個隊列來分配不一樣優先級的任務,也能夠採用 stealing 的機制來提升線程的利用率。
再提供配置來代表此線程池是 IO 密集仍是 CPU 密集型來改變任務的執行策略。
超負荷的方案能夠有多種,包括丟棄任務、拒絕任務並拋出異常、丟棄最舊的任務或自定義等等。
線程池埋好點暴露出用於監控的接口,如已處理任務數、待處理任務數、正在運行的線程數、拒絕的任務數等等信息。
我以爲基本上這樣答就差很少了,等着面試官的追問就好。
注意不須要跟面試官解釋什麼叫核心線程數之類的,都懂的不必。
固然這種開放型問題仍是仁者見仁智者見智,我這個不是標準答案,僅供參考。
關於線程池的一點碎碎念
線程池的好處咱們都知道了,可是不是任什麼時候刻都上線程池的,我看過一些奇怪的代碼,就是爲了用線程池而用線程池...
還有須要根據不一樣的業務劃分不一樣的線程池,否則會存在一些耗時的業務影響了另外一個業務致使這個業務崩了,而後都崩了的狀況,因此要作好線程池隔離。
最後
好了,有關線程池的知識點和一些常見的一些面試題應該都涉及到了吧,若是還有別的啥角度刁鑽的面試題,歡迎留言提出,我們一塊兒研究研究。
相信看了這篇文章以後,關於線程池出去面試能夠開始吹了!
若是以爲文章不錯,來個點贊和在看唄!
歡迎關注個人公衆號【yes的練級攻略】,更多硬核文章等你來讀。
我是yes,從一點點到億點點,咱們下篇見。
推薦閱讀:
本文分享自微信公衆號 - yes的練級攻略(yes_java)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。