深入理解JAVA線程池

1、 什麼是線程池

線程池,就是一個池子,任務提交的到線程池後,線程池會在池子裏邊找有沒有空閒的線程,如果沒有,就會進入等待狀態,有就會分配一個空閒的線程來接受這個任務,當服務執行完,從新放回到線程池,不需要銷燬。在這種模式下,系統大大減少了創建線程個銷燬線程的資源開銷,而且一個線程可以用來執行多個任務,我們可以根據系統的配置靈活調整線程池的大小,從而更高效的執行任務。

2、 爲什麼要用線程池

多線程併發開發中,線程的數量較多,且每個線程執行一定的時間後就結束了,下一個線程任務到來還需要重新創建線程,這樣線程數量特別龐大的時候,頻繁的創建線程和銷燬線程需要一定時間而且增加系統的額外開銷。基於這樣的場景,線程池就出現了,線程池可以做到一個線程的任務處理完可以接受下一個任務,並不需要頻繁的創建銷燬,這樣大大節省了時間和系統的開銷。

3、 線程池的作用

  1. 利用線程池管理並複用線程,控制最大併發數等。
  2. 實現任務隊列緩存策略和拒絕策略。
  3. 實現某些與實踐相關的功能。
  4. 隔離線程環境。比如,交易服務和搜索服務在同一臺服務器上,分別開啓兩個線程池,交易線程的資源消耗明顯要大;因此需要配置獨立的線程池,將較慢的交易服務和搜索服務隔離開,避免各服務線程相互影響。

4、線程生命週期有哪些狀態

在這裏插入圖片描述

5、 創建線程的方式有哪些

  1. 繼承Thread類
  2. 實現Runable接口
  3. 實現Callable接口
  4. 使用線程池創建線程
    在這裏插入圖片描述

6、 線程池代碼要素


(1) Executor接口 ,中含有void execute(Runnable command);方法,用於執行給定的命令。
(2) ExecutorService 接口,一種執行器,它提供管理終止的方法,以及可以產生用於跟蹤一個或多個異步任務的進度的未來的方法。包含 Future submit(Callable task)方法。
(3) ThreadPoolExecutor類,可用於自定義線程池。
(4) Executors工廠類,提供了很多實現線程池的方式。

7、 線程池有哪幾種

(1)Executors.newWorkStealingPool :jdk1.8新增,創建一個線程池,該線程池維護足夠多的線程以支持給定的並行級別,可以使用多個隊列減少競爭。次構造方法中把cpu的數量設置爲默認的並行度。
(2)Executors.newCachedThreadPool****:是一個會根據需要創建新線程的線程池,無界線程池,核心線程爲0,maximumPoolSize爲Integer.MAX_VALUE,是高度可伸縮的線程池。keepAliveTime默認60s,如果工作線程處於空閒狀態,則回收線程。如果任務數增加,則再次創建新線程處理任務。
使用場景:用於併發執行大量短期的小任務。
(3)Executors. newFixedThreadPool: 創建指定大小的線程池,這個大小既是核心線程大小,也是最大線程數,不存在空閒線程,所以keepAliveTime=0,如果超出大小,放入block隊列,即LinkedBlockingQueue隊列。有OOM風險。適用於需要保證所有提交的任務都要被執行的情況。
使用場景:FixedThreadPool 適用於處理CPU密集型的任務,確保CPU在長期被工作線程使用的情況下,儘可能的少的分配線程,即適用執行長期的任務。
4)Executors. newSingleThreadPool:創建一個單線程的線程池,相當於單線程 穿行執行所有任務,保證按任務的提交順序依次執行。
使用場景:適用於串行執行任務的場景,一個任務一個任務地執行。
**(5)Executors.newScheduledThreadPool:**線程數最大至Integer.MAX_VALUE,存在OOM風險。他是ScheduledExecutorService接口家族的實現類,支持定時及週期性任務執行。相比Timer,ScheduledExecutorService更安全,功能更強大,與newCachedThreadPool的區別是不回收工作線程。
**使用場景:**週期性執行任務的場景,需要限制線程數量的場景。

8、 線程池都有哪幾種工作隊列?

ArrayBlockingQueue(有界隊列)是一個用數組實現的有界阻塞隊列,按FIFO排序量。

LinkedBlockingQueue(可設置容量隊列)基於鏈表結構的阻塞隊列,按FIFO排序任務,容量可以選擇進行設置,不設置的話,將是一個無邊界的阻塞隊列,最大長度爲Integer.MAX_VALUE,吞吐量通常要高於ArrayBlockingQuene;newFixedThreadPool線程池使用了這個隊列。

DelayQueue(延遲隊列)是一個任務定時週期的延遲執行的隊列。根據指定的執行時間從小到大排序,否則根據插入到隊列的先後排序。newScheduledThreadPool線程池使用了這個隊列。
PriorityBlockingQueue(優先級隊列)是具有優先級的無界阻塞隊列;

SynchronousQueue(同步隊列)一個不存儲元素的阻塞隊列,每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於LinkedBlockingQuene,newCachedThreadPool線程池使用了這個隊列。

9、 ThreadPoolExecutor參數分析

	public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

在這裏插入圖片描述
在這裏插入圖片描述

9、線程池工作原理

線程池工作原理:任務開始以後,會創建線程執行任務,當創建的線程達到coreSize後,就會將後續的任務放到隊列中讓核心線程去執行,當任務在隊列中放滿了,就會創建額外的線程去執行新來的任務,當新來的任務都執行完了,這些額外線程也會去執行隊列中的任務。加入隊列中的任務都執行完了,那麼額外線程空閒了keeplivetime這段時間後,會自動銷燬。如果額外線程和核心線程數量 總和達到最大線程數,並且都在很繁忙的執行任務,隊列滿了,這時候新來的任務會被執行拒絕策略,比如拋異常、拋棄任務、拋棄舊任務等等。

10、使用線程池應注意的幾點

在這裏插入圖片描述

11、在線程池中使用無界阻塞隊列會產生什麼問題?

比如newFixedThreadPool使用了無界的阻塞隊列LinkedBlockingQueue,如果線程獲取一個任務後,任務的執行時間比較長(比如,上面demo設置了10秒),會導致隊列的任務越積越多,導致機器內存使用不停飆升, 最終導致OOM。

12、你知道如果線程池的隊列滿了之後,會發生什麼事情嗎?

如果是有界隊列,最大線程數爲200,如以下這種情況:
ArrayBlockingQueue(200)
coresize=10;
maxmunpoolsize=200
如果隊列滿了,就會創建額外的線程處理新的任務以及隊列中的任務,不會出現內存溢出的情況。但是如果隊列滿了,線程都在執行任務,新的任務來了就會通過拒絕策略拒絕掉。可以自定義reject策略,將無法執行的任務持久化的寫到磁盤裏面去,後臺專門啓動一個線程,後續等到工作線程的工作負載降低了,可以慢慢的從磁盤中讀取任務重新提取到線程池執行。

如果是有界隊列,最大線程數爲Integer.Max_VALUE:可以無限制的創建出很多的線程幾千甚至幾萬線程,每個線程都有自己的棧內存,佔用一定的內存空間,也會導致內存資源耗盡,系統崩潰掉。即使內存沒有崩潰,也會導致CPU負載過高,系統崩潰。

用無界隊列可能會導致cpu負載過高,內存資源耗盡,OOM。

可以根據業務情況,選擇隊列。

13、如果線上機器突然宕機,線程池的阻塞隊列中的請求怎麼辦?

必然會導致線程池中的隊列中積壓的任務實際上來說都會丟失。

如果你要提交一個任務到線程池裏去,可以在提交之前,往數據庫裏面插入這個任務信息,更新狀態爲:未提交,已提交,已完成。提交到隊列成功後,更新數據庫狀態爲已提交。

系統宕機後重啓,後臺開一個線程去掃描數據庫未提交和已提交狀態的任務,把任務的信息讀出來放到線程池中去,繼續進行執行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章