## 38 線程池源碼面試題
## 引導語
線程池在日常面試中占比很大,主要是因為線程池內容涉及的知識點較廣,比如涉及到隊列、線程、鎖等等,所以很多面試官喜歡把線程池作為問題的起點,然后延伸到其它內容,由于我們專欄已經說過隊列、線程、鎖面試題了,所以本章面試題還是以線程池為主。
### 1:說說你對線程池的理解?
答:答題思路從大到小,從全面到局部,總的可以這么說,線程池結合了鎖、線程、隊列等元素,在請求量較大的環境下,可以多線程的處理請求,充分的利用了系統的資源,提高了處理請求的速度,細節可以從以下幾個方面闡述:
1. ThreadPoolExecutor 類結構;
2. ThreadPoolExecutor coreSize、maxSize 等重要屬性;
3. Worker 的重要作用;
4. submit 的整個過程。
通過以上總分的描述,應該可以說清楚對線程池的理解了,如果是面對面面試的話,可以邊說邊畫出線程池的整體架構圖(見《ThreadPoolExecutor 源碼解析》)。
### 2:ThreadPoolExecutor、Executor、ExecutorService、Runnable、Callable、FutureTask 之間的關系?
答:以上 6 個類可以分成兩大類:一種是定義任務類,一種是執行任務類。
1. 定義任務類:Runnable、Callable、FutureTask。Runnable 是定義無返回值的任務,Callable 是定義有返回值的任務,FutureTask 是對 Runnable 和 Callable 兩種任務的統一,并增加了對任務的管理功能;
2. 執行任務類:ThreadPoolExecutor、Executor、ExecutorService。Executor 定義最基本的運行接口,ExecutorService 是對其功能的補充,ThreadPoolExecutor 提供真正可運行的線程池類,三個類定義了任務的運行機制。
日常的做法都是先根據定義任務類定義出任務來,然后丟給執行任務類去執行。
### 3:說一說隊列在線程池中起的作用?
答:作用如下:
1. 當請求數大于 coreSize 時,可以讓任務在隊列中排隊,讓線程池中的線程慢慢的消費請求,實際工作中,實際線程數不可能等于請求數,隊列提供了一種機制讓任務可排隊,起一個緩沖區的作用;
2. 當線程消費完所有的線程后,會阻塞的從隊列中拿數據,通過隊列阻塞的功能,使線程不消亡,一旦隊列中有數據產生后,可立馬被消費。
### 4:結合請求不斷增加時,說一說線程池構造器參數的含義和表現?
答:線程池構造器各個參數的含義如下:
1. coreSize 核心線程數;
2. maxSize 最大線程數;
3. keepAliveTime 線程空閑的最大時間;
4. queue 有多種隊列可供選擇,比如:1:SynchronousQueue,為了避免任務被拒絕,要求線程池的 maxSize 無界,缺點是當任務提交的速度超過消費的速度時,可能出現無限制的線程增長;2:LinkedBlockingQueue,無界隊列,未消費的任務可以在隊列中等待;3:ArrayBlockingQueue,有界隊列,可以防止資源被耗盡;
5. 線程新建的 ThreadFactory 可以自定義,也可以使用默認的 DefaultThreadFactory,DefaultThreadFactory 創建線程時,優先級會被限制成 NORM_PRIORITY,默認會被設置成非守護線程;
6. 在 Executor 已經關閉或對最大線程和最大隊列都使用飽和時,可以使用 RejectedExecutionHandler 類進行異常捕捉,有如下四種處理策略:ThreadPoolExecutor.AbortPolicy、ThreadPoolExecutor.DiscardPolicy、ThreadPoolExecutor.CallerRunsPolicy、ThreadPoolExecutor.DiscardOldestPolicy。
當請求不斷增加時,各個參數起的作用如下:
1. 請求數 < coreSize:創建新的線程來處理任務;
2. coreSize <= 請求數 && 能夠成功入隊列:任務進入到隊列中等待被消費;
3. 隊列已滿 && 請求數 < maxSize:創建新的線程來處理任務;
4. 隊列已滿 && 請求數 >= maxSize:使用 RejectedExecutionHandler 類拒絕請求。
### 5:coreSize 和 maxSize 可以動態設置么,有沒有規則限制?
答:一般來說,coreSize 和 maxSize 在線程池初始化時就已經設定了,但我們也可以通過 setCorePoolSize、setMaximumPoolSize 方法動態的修改這兩個值。
setCorePoolSize 的限制見如下源碼:
```
// 如果新設置的值小于 coreSize,多余的線程在空閑時會被回收(不保證一定可以回收成功) // 如果大于 coseSize,會新創建線程
public void setCorePoolSize(int corePoolSize) { if (corePoolSize < 0) throw new IllegalArgumentException(); int delta = corePoolSize - this.corePoolSize; this.corePoolSize = corePoolSize; // 活動的線程大于新設置的核心線程數 if (workerCountOf(ctl.get()) > corePoolSize) // 嘗試將可以獲得鎖的 worker 中斷,只會循環一次 // 最后并不能保證活動的線程數一定小于核心線程數 interruptIdleWorkers(); // 設置的核心線程數大于原來的核心線程數 else if (delta > 0) { // 并不清楚應該新增多少線程,取新增核心線程數和等待隊列數據的最小值,夠用就好 int k = Math.min(delta, workQueue.size()); // 新增線程直到k,如果期間等待隊列空了也不會再新增 while (k-- > 0 && addWorker(null, true)) { if (workQueue.isEmpty()) break; } } }
```
setMaximumPoolSize 的限制見如下源碼:
```
// 如果 maxSize 大于原來的值,直接設置。 // 如果 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:說一說對于線程空閑回收的理解,源碼中如何體現的?
答:空閑線程回收的時機:如果線程超過 keepAliveTime 時間后,還從阻塞隊列中拿不到任務(這種情況我們稱為線程空閑),當前線程就會被回收,如果 allowCoreThreadTimeOut 設置成 true,core thread 也會被回收,直到還剩下一個線程為止,如果 allowCoreThreadTimeOut 設置成 false,只會回收非 core thread 的線程。
線程在任務執行完成之后,之所有沒有消亡,是因為阻塞的從隊列中拿任務,在 keepAliveTime 時間后都沒有拿到任務的話,就會打斷阻塞,線程直接返回,線程的生命周期就結束了,JVM 會回收掉該線程對象,所以我們說的線程回收源碼體現就是讓線程不在隊列中阻塞,直接返回了,可以見 ThreadPoolExecutor 源碼解析章節第三小節的源碼解析。
### 7:如果我想在線程池任務執行之前和之后,做一些資源清理的工作,可以么,如何做?
答:可以的,ThreadPoolExecutor 提供了一些鉤子函數,我們只需要繼承 ThreadPoolExecutor 并實現這些鉤子函數即可。在線程池任務執行之前實現 beforeExecute 方法,執行之后實現 afterExecute 方法。
### 8:線程池中的線程創建,拒絕請求可以自定義實現么?如何自定義?
答:可以自定義的,線程創建默認使用的是 DefaultThreadFactory,自定義話的只需要實現 ThreadFactory 接口即可;拒絕請求也是可以自定義的,實現 RejectedExecutionHandler 接口即可;在 ThreadPoolExecutor 初始化時,將兩個自定義類作為構造器的入參傳遞給 ThreadPoolExecutor 即可。
### 9:說說你對 Worker 的理解?
答:詳見《ThreadPoolExecutor 源碼解析》中 1.4 小節。
### 10:說一說 submit 方法執行的過程?
答:詳見《ThreadPoolExecutor 源碼解析》中 2 小節。
### 11:說一說線程執行任務之后,都在干啥?
答:線程執行任務完成之后,有兩種結果:
1. 線程會阻塞從隊列中拿任務,沒有任務的話無限阻塞;
2. 線程會阻塞從隊列中拿任務,沒有任務的話阻塞一段時間后,線程返回,被 JVM 回收。
### 12:keepAliveTime 設置成負數或者是 0,表示無限阻塞?
答:這種是不對的,如果 keepAliveTime 設置成負數,在線程池初始化時,就會直接報 IllegalArgumentException 的異常,而設置成 0,隊列如果是 LinkedBlockingQueue 的話,執行 workQueue.poll (keepAliveTime, TimeUnit.NANOSECONDS) 方法時,如果隊列中沒有任務,會直接返回 null,導致線程立馬返回,不會無限阻塞。
如果想無限阻塞的話,可以把 keepAliveTime 設置的很大,把 TimeUnit 也設置的很大,接近于無限阻塞。
### 13:說一說 Future.get 方法是如何拿到線程的執行結果的?
答:我們需要明確幾點:
1. submit 方法的返回結果實際上是 FutureTask,我們平時都是針對接口編程,所以使用的是 Future.get 來拿到線程的執行結果,實際上是 FutureTask.get ,其方法底層是從 FutureTask 的 outcome 屬性拿值的;
2. 《ThreadPoolExecutor 源碼解析》中 2 小節中詳細說明了 submit 方法最終會把線程的執行結果賦值給 outcome。
結合 1、2,當線程執行完成之后,自然就可以從 FutureTask 的 outcome 屬性中拿到值。
### 14:總結
如果我們弄清楚 ThreadPoolExecutor 的原理之后,線程池的面試題都很簡單,所以建議大家多看看 《ThreadPoolExecutor 源碼解析》這小節。
- 前言
- 第1章 基礎
- 01 開篇詞:為什么學習本專欄
- 02 String、Long 源碼解析和面試題
- 03 Java 常用關鍵字理解
- 04 Arrays、Collections、Objects 常用方法源碼解析
- 第2章 集合
- 05 ArrayList 源碼解析和設計思路
- 06 LinkedList 源碼解析
- 07 List 源碼會問哪些面試題
- 08 HashMap 源碼解析
- 09 TreeMap 和 LinkedHashMap 核心源碼解析
- 10 Map源碼會問哪些面試題
- 11 HashSet、TreeSet 源碼解析
- 12 彰顯細節:看集合源碼對我們實際工作的幫助和應用
- 13 差異對比:集合在 Java 7 和 8 有何不同和改進
- 14 簡化工作:Guava Lists Maps 實際工作運用和源碼
- 第3章 并發集合類
- 15 CopyOnWriteArrayList 源碼解析和設計思路
- 16 ConcurrentHashMap 源碼解析和設計思路
- 17 并發 List、Map源碼面試題
- 18 場景集合:并發 List、Map的應用場景
- 第4章 隊列
- 19 LinkedBlockingQueue 源碼解析
- 20 SynchronousQueue 源碼解析
- 21 DelayQueue 源碼解析
- 22 ArrayBlockingQueue 源碼解析
- 23 隊列在源碼方面的面試題
- 24 舉一反三:隊列在 Java 其它源碼中的應用
- 25 整體設計:隊列設計思想、工作中使用場景
- 26 驚嘆面試官:由淺入深手寫隊列
- 第5章 線程
- 27 Thread 源碼解析
- 28 Future、ExecutorService 源碼解析
- 29 押寶線程源碼面試題
- 第6章 鎖
- 30 AbstractQueuedSynchronizer 源碼解析(上)
- 31 AbstractQueuedSynchronizer 源碼解析(下)
- 32 ReentrantLock 源碼解析
- 33 CountDownLatch、Atomic 等其它源碼解析
- 34 只求問倒:連環相扣系列鎖面試題
- 35 經驗總結:各種鎖在工作中使用場景和細節
- 36 從容不迫:重寫鎖的設計結構和細節
- 第7章 線程池
- 37 ThreadPoolExecutor 源碼解析
- 38 線程池源碼面試題
- 39 經驗總結:不同場景,如何使用線程池
- 40 打動面試官:線程池流程編排中的運用實戰
- 第8章 Lambda 流
- 41 突破難點:如何看 Lambda 源碼
- 42 常用的 Lambda 表達式使用場景解析和應用
- 第9章 其他
- 43 ThreadLocal 源碼解析
- 44 場景實戰:ThreadLocal 在上下文傳值場景下的實踐
- 45 Socket 源碼及面試題
- 46 ServerSocket 源碼及面試題
- 47 工作實戰:Socket 結合線程池的使用
- 第10章 專欄總結
- 48 一起看過的 Java 源碼和面試真題