12 連環炮、一張圖快速搞定線程池

連環炮繼續走起,今天我給大家總結了線程池的 12 連環炮。

1、爲什麼要創建線程池?

2、創建線程池有哪些方式?

3、Executors 能創建幾種常用線程池?

4、線程池有哪些參數?

5,能說說線程池原理嗎?

6、線程池有哪些拒絕策略?

7、線程池中使用到了阻塞隊列,那你知道有哪些阻塞隊列?

8、線程池中的核心線程如何設置呢?

9、知道線程池有哪些狀態嗎?

10、線程池中的線程是如何複用的?

11、Java 線程池中 submit() 和 execute() 方法有什麼區別?

12、在工作中,有使用過線程池嗎?

線程池整體圖

1、爲什麼要創建線程池?

學了線程不懂線程池,那是遠遠不夠的,因爲我們通常使用線程的時候,都會涉及到線程池。

數據庫也有連接池,也就是大家所講的池化技術。

線程也有線程池,對於池的概念大家一定把握好兩個關鍵:

爲什麼要創建線程池,回答這個問題,我們可以說一下它的三個優點。

第一個,降低資源消耗。通過重複利用線程池中的線程,能規避我們不斷創建線程和不斷銷燬線程造成的資源消耗。

第二個,提高響應速度。當任務到達線程池中,任務可以不需要等待線程創建就能立即執行。

第三個,提高線程的可管理性。大家都知道,線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性。使用線程池,可以進行統一的分配、調優和監控。

回答完上面三個線程池的好處以後,面試官肯定會繼續問:創建線程池的方式有哪些?

2、創建線程池有哪些方式?

在 java 中創建線程有兩種方式:

這兩個都在 juc 目錄下面。其中,Executors 可以理解爲一個工具類,是用來生成線程池的。另外,Executors 的大部分創建線程方式最終使用的都是 ThreadPoolExecutor。

並且在阿里規開發規範中,建議不要使用 Executors 來創建線程池。因爲線程池的類型以及線程相關參數都是在 Executors 中就已經封裝好了,如果我們在代碼中使用不當,可能會造成系統出問題。建議使用 ThreadPoolExecutor 創建線程,然後,根據自己的業務實際情況進行創建線程池。

3、Executors 能創建幾種常用線程池?

上面我們說到,創建線程池的兩種方式。

其實,大部分面試官想要我們回答的是 Executors 創建線程池的方式有哪幾種?所以肯定會繼續追問,Executors 創建線程池的幾種方式。今天就來看看,如何回答這個問題。

我們可以把 Executors 類理解成爲一個工廠類,該類可以爲我們提供六種創建線程池的方法:

第 1 種,newSingleThreadExecutor,創建了一個單線程的線程池,此線程池保證了所有任務的執行順序都是按照任務的提交順序執行。也就是說,這個池子裏只有一個線程。

第 2 種,newFixedThreadPool,創建指定數量線程的線程池;

第 3 種,newCachedThreadPool,創建一個可緩存的線程池,伸縮性、動態調整,60s 回收一次;

第 4 種,newScheduledThreadPool,創建一個大小無限的線程池;

第 5 種,newSingleThreadScheduledExecutor(),創建一個單例線程池,定期或延時執行任務,很多框架中使用此實現定時心跳檢測。

第 6 種,newWorkStealingPool,這是 Java 8 新增創建線程池的方法。

回答完上面內容後,面試就會問:線程池有哪些參數?

4、線程池有哪些參數?

這個問題也算是老掉牙的面試題,但是面試中頻率是相當高,經久不衰。

線程池一共有 7 個參數:

第 1 個,maximumPoolSize,指的是最大線程數

第 2 個,corePoolSize,指的是核心線程數

第 3 個,keepAliveTime ,指的是最大線程活躍時間

第 4 個,unit: 指定了 keepAliveTime 的單位,可以爲毫秒,秒,分,小時等;

第 5 個,workQueue: 存儲未執行的任務的隊列;

第 6 個,threadFactory: 創建線程的工廠,如果未指定則使用默認的線程工廠;

第 7 個,handler: 指定了當任務隊列已滿,並且沒有可用線程執行任務時對新添加的任務的處理策略;

面試中,如果你能說出 maximumPoolSize、corePoolSize、keepAliveTime 、workQueue、handler 五個核心參數也算可以了,但還是建議大家把七個參數都回答出來。

5,能說說線程池原理嗎?

這道題,我本人在面試中最喜歡用生活中的例子來回答,請看我是怎麼回答的:

一家工廠,訂單來了,正式員工們就開始生產零件。但是訂單越來越多,正式員工處理不來了,就先把任務放到倉庫裏,但是遇到訂單爆棚的時候,倉庫也都放不下了,這時候,工廠就會想到找點臨時工來幫忙生產零件,如果訂單實在是太多了,這時候工廠可能就會想辦法拒絕那些不是很有利潤的訂單。同時,生意也有慘淡的時候,這時候工廠也許就會清理臨時工,讓他們等以後忙的時候再來幫忙。

在這裏,訂單就是我們創建的線程,工廠就就是線程池,正式員工就是核心線程,臨時員工就是最大線程,倉庫就是阻塞隊列,訂單實在太多了,就會考慮到訂單的利潤,也就是說,利潤太少了那就不幹了,這就是拒絕策略。

建議結合前面整體圖進行理解。

6、線程池有哪些拒絕策略?

線程池核心參數中,有個很重要的參數就是拒絕策略,面試官也是非常喜歡問的,大家可以這麼回答。

線程池中主要有 4 種拒絕策略:

第 1 種,AbortPolicy,這是默認策略,指的是丟棄任務,拋出異常;

第 2 種,CallerRunsPolicy,簡單的說,就是後面排隊的線程就在那等着,被拒絕的任務在主線程中運行,所以主線程就被阻塞了,別的任務只能在被拒絕的任務執行完後,纔會繼續被提交到線程池中。

第 3 種,DiscardOldestPolicy,指的是丟棄等待隊列中最久的任務,並且執行當前任務;

第 4 種,DiscardPolicy,直接丟棄任務,也不拋異常。

我們說完了線程池核心參數中一個拒絕策略,也有的面試官喜歡問另外一個參數,那就是阻塞隊列。

7、線程池中使用到了阻塞隊列,那你知道有哪些阻塞隊列?

這個問題,也屬於 java 集合框架的內容,所以,還是很有必要掌握的。

JDK7 及以後一共有 7 種阻塞隊列:

第 1 種,ArrayBlockingQueue ,由數組結構組成的有界阻塞隊列。

第 2 種,LinkedBlockingQueue ,由鏈表結構組成的有界阻塞隊列。

第 3 種,PriorityBlockingQueue ,支持優先級排序的無界阻塞隊列。

第 4 種,DelayQueue,使用優先級隊列實現的無界阻塞隊列。

第 5 種,SynchronousQueue,不存儲元素的阻塞隊列。

第 6 種,LinkedTransferQueue,由鏈表結構組成的無界阻塞隊列。

第 7 種,LinkedBlockingDeque,由鏈表結構組成的雙向阻塞隊列。

另外,Executors 中使用最多的是 LinkedBlockingDeque,還用了 SynchronousQueue。

回答完以上七種後,請記住把 Executors 中使用的兩種也強調一下,表示你對此非常熟悉。

還有可能會繼續問一個非常重要的參數,那就是核心線程數。

8、線程池中的核心線程如何設置呢?

回答這個問題,我們首先得知道,線程和 CPU 有關,所以,如何設置核心線程數,肯定也是和 CPU 有關的,核心線程數設置和任務的類型也有關,任務類型有兩種:

一種是 CPU 密集型, 比如像加解密、壓縮、計算等一系列需要大量耗費 CPU 資源的任務,大部分場景下都是純 CPU 計算。所以,核心線程數可以設置爲:核心線程數 = CPU 個數 + 1。

另外一種,就是 IO 密集型, 比如像 MySQL 數據庫、文件的讀寫、網絡通信等任務,這類任務不會特別消耗 CPU 資源,但是 IO 操作比較耗時,會佔用比較多的時間。所以,核心線程數可以設置爲:核心線程數 = CPU 個數 * 2。

這個只是個理論值,具體設置大小,建議在本地、測試、準生產環境下調試出相對最優參數大小。

回答完這些後,有的面試官可能會問:你知道線程池的狀態嗎?

9、知道線程池有哪些狀態嗎?

很多人,平時估計都不知道線程池居然也有狀態,都只知道線程狀態。所以這種問題如果不準備,只要被問到必掛。

線程池有 5 種狀態,

第 1 種,RUNNING,指的是線程池的初始化狀態,可添加待執行的任務。

第 2 種,SHUTDOWN,指的是線程池處於待關閉狀態,不接收新任務,僅處理已接收的任務。

第 3 種,STOP,指的是線程池立即關閉,不接收新的任務,放棄緩存隊列中的任務並且中斷正在處理的任務。

第 4 種,TIDYING,指的是線程池自主整理狀態,我們可以調用 terminated() 方法進行線程池整理。

第 5 種,TERMINATED,指的是線程池終止狀態。

回答完線程池狀態後,下面這個問題是非常經典的,也基本上是問中高級的。

10、線程池中的線程是如何複用的?

這裏的線程複用指的是線程池的中線程被重複利用,這裏注意了,很多人也許都還沒明白這點!再強調一次,

我們手動創建的線程放到線程池中,其實,這時候它變成了一個任務。

線程池執行這些任務的時候,不再是調用線程(任務)的 start() 方法,而是由線程池中的線程去執行這些任務。

然後線程池中的這些線程就不斷地去循環,執行我們丟進去的任務,並調用這些任務的 run() 方法。此時調用的 run() 方法就相當於調用一個普通對象的 put 方法,通過重複使用這些固定線程來執行所有任務。

11、Java 線程池中 submit() 和 execute() 方法有什麼區別?

不知道你是否還記得,我們創建線程,然後把線程提交到線程池中時候,通常有兩種方法:submit 和 execute。所以面試官很有可能會問這個問題,這個問題也很有可能出現在筆試中。

回答這個問題,我們得先說說兩者的相同點,

submit() 和 execute() 方法都是用來提交任務的,指的是把我們創建的任務提交到線程池中。

兩者的不同點在於,調用 execute() 方法提交任務不能拿到任務的返回值,而調用 submit() 可以使用 Future 接收線程池執行任務的返回值。

這裏就可以聯想到,我們創建線程的方式中,有的方式可以拿到線程返回值,有的拿不到。

另外,execute() 方法是 ThreadPoolExecutor 的方法,而 submit() 方法是 ThreadPoolExecutor 的父類 AbstractExecutorService 中的方法。

12、在工作中,有使用過線程池嗎?

這道問題,對於 99% 的人都很害怕,因爲工作中可能真的沒用過,所以,心裏很沒底兒。有的人用過了,卻沒注意項目中是怎麼樣用,可能是組長或者其他人寫了個工具類,自己用也是拷貝別人代碼改改,然後也挺好的。

其實大家可以編造自己的業務場景哈,即使沒有,你也可以自己編造點呀,不圖羊嘛。這世道就是套路多多滴。這個沒有具體答案,答案還得靠你結合你所做項目來回答哈。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/MN0bkcHC1JCEHSR78Vv_Pg