執行緒池的五種狀態及創建執行緒池的幾種方式

在這裡插入圖片描述

上篇《Java執行緒的6種狀態詳解及創建執行緒的4種方式
前言:我們都知道,執行緒是稀有資源,系統頻繁創建會很大程度上影響伺服器的使用效率,如果不加以限制,很容易就會把伺服器資源耗盡。所以,我們可以通過創建執行緒池來管理這些執行緒,提升對執行緒的使用率。

1、什麼是執行緒池?

簡而言之,執行緒池就是管理執行緒的一個容器,有任務需要處理時,會相繼判斷核心執行緒數是否還有空閑、執行緒池中的任務隊列是否已滿、是否超過執行緒池大小,然後調用或創建執行緒或者排隊,執行緒執行完任務後並不會立即被銷毀,而是仍然在執行緒池中等待下一個任務,如果超過存活時間還沒有新的任務就會被銷毀,通過這樣復用執行緒從而降低開銷。

2、使用執行緒池有什麼優點?

可能有人就會問了,使用執行緒池有什麼好處嗎?那不用說,好處自然是有滴。大概有以下:
1、提升執行緒池中執行緒的使用率,減少對象的創建、銷毀。
2、執行緒池的伸縮性對性能有較大的影響,使用執行緒池可以控制執行緒數,有效的提升伺服器的使用資源,避免由於資源不足而發生宕機等問題。(創建太多執行緒,將會浪費一定的資源,有些執行緒未被充分使用;銷毀太多執行緒,將導致之後浪費時間再次創建它們;創建執行緒太慢,將會導致長時間的等待,性能變差;銷毀執行緒太慢,導致其它執行緒資源飢餓。)

3、執行緒池的核心工作流程(重要)

我們要使用執行緒池得先了解它是怎麼工作的,流程如下圖,廢話不多說看圖就行。核心就是復用執行緒,降低開銷。
執行緒池的工作流程

4、執行緒池的五種狀態生命周期

  • RUNNING :能接受新提交的任務,並且也能處理阻塞隊列中的任務。
  • SHUTDOWN:關閉狀態,不再接受新提交的任務,但卻可以繼續處理阻塞隊列中已保存的任務。在執行緒池處於 RUNNING 狀態時,調用 shutdown() 方法會使執行緒池進入到該狀態。(finalize() 方法在執行過程中也會調用 shutdown() 方法進入該狀態)。
  • STOP:不能接受新任務,也不處理隊列中的任務,會中斷正在處理任務的執行緒。在執行緒池處於 RUNNING 或 SHUTDOWN 狀態時,調用 shutdownNow() 方法會使執行緒池進入到該狀態。
  • TIDYING:如果所有的任務都已終止了,workerCount (有效執行緒數) 為0,執行緒池進入該狀態後會調用 terminated() 方法進入 TERMINATED 狀態。
  • TERMINATED:在 terminated() 方法執行完後進入該狀態,默認 terminated() 方法中什麼也沒有做。
    執行緒池的生命周期流程圖

5、創建執行緒池的幾種方式

  • 通過 Executors 工廠方法創建
  • 通過 new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) 自定義創建
    相對而言,更建議用第二個創建執行緒池,Executors 創建的執行緒池內部很多地方用到了無界任務隊列,在高並發場景下,無界任務隊列會接收過多的任務對象,嚴重情況下會導致 JVM 崩潰,一些大廠也是禁止使用 Executors 工廠方法去創建執行緒池。newFixedThreadPool 和 newSingleThreadExecutor 的主要問題是堆積的請求處理隊列可能會耗費非常大的記憶體,甚至 OOM;newCachedThreadPool 和 newScheduledThreadPool 的主要問題是執行緒數最大數是 Integer.MAX_VALUE,可能會創建數量非常多的執行緒,甚至 OOM。

5.1、Executors 五個工廠方法創建不同執行緒池的區別

在這裡插入圖片描述
1、newCachedThreadPool()(工作隊列使用的是 SynchronousQueue)
創建一個執行緒池,如果執行緒池中的執行緒數量過大,它可以有效的回收多餘的執行緒,如果執行緒數不足,那麼它可以創建新的執行緒。
不足:這種方式雖然可以根據業務場景自動的擴展執行緒數來處理我們的業務,但是最多需要多少個執行緒同時處理卻是我們無法控制的。
優點:如果當第二個任務開始,第一個任務已經執行結束,那麼第二個任務會復用第一個任務創建的執行緒,並不會重新創建新的執行緒,提高了執行緒的復用率。
作用:該方法返回一個可以根據實際情況調整執行緒池中執行緒的數量的執行緒池。即該執行緒池中的執行緒數量不確定,是根據實際情況動態調整的。
2、newFixedThreadPool()(工作隊列使用的是 LinkedBlockingQueue)
這種方式可以指定執行緒池中的執行緒數。如果滿了後又來了新任務,此時只能排隊等待。
優點:newFixedThreadPool 的執行緒數是可以進行控制的,因此我們可以通過控制最大執行緒來使我們的伺服器達到最大的使用率,同時又可以保證即使流量突然增大也不會佔用伺服器過多的資源。
作用:該方法返回一個固定執行緒數量的執行緒池,該執行緒池中的執行緒數量始終不變,即不會再創建新的執行緒,也不會銷毀已經創建好的執行緒,自始自終都是那幾個固定的執行緒在工作,所以該執行緒池可以控制執行緒的最大並發數。
3、newScheduledThreadPool()
該執行緒池支援定時,以及周期性的任務執行,我們可以延遲任務的執行時間,也可以設置一個周期性的時間讓任務重複執行。該執行緒池中有以下兩種延遲的方法。
scheduleAtFixedRate 不同的地方是任務的執行時間,如果間隔時間大於任務的執行時間,任務不受執行時間的影響。如果間隔時間小於任務的執行時間,那麼任務執行結束之後,會立馬執行,至此間隔時間就會被打亂。
scheduleWithFixedDelay 的間隔時間不會受任務執行時間長短的影響。
作用:該方法返回一個可以控制執行緒池內執行緒定時或周期性執行某任務的執行緒池。
4、newSingleThreadExecutor()
這是一個單執行緒池,至始至終都由一個執行緒來執行。
作用:該方法返回一個只有一個執行緒的執行緒池,即每次只能執行一個執行緒任務,多餘的任務會保存到一個任務隊列中,等待這一個執行緒空閑,當這個執行緒空閑了再按 FIFO 方式順序執行任務隊列中的任務。
5、newSingleThreadScheduledExecutor()
只有一個執行緒,用來調度任務在指定時間執行。
作用:該方法返回一個可以控制執行緒池內執行緒定時或周期性執行某任務的執行緒池。只不過和上面的區別是該執行緒池大小為 1,而上面的可以指定執行緒池的大小。
使用示例:

//創建一個會根據需要創建新執行緒的執行緒池
ExecutorService executor= Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
	executor.submit(new Runnable() {
		@Override
        public void run() {
        	System.out.println(i);
        }
    });
}

這五種執行緒池都是直接或者間接獲取的 ThreadPoolExecutor 實例 ,只是實例化時傳遞的參數不一樣。所以如果 Java 提供的執行緒池滿足不了我們的需求,我們可以通過 ThreadPoolExecutor 構造方法創建自定義執行緒池。

5.2、ThreadPoolExecutor 構造方法參數詳解

public ThreadPoolExecutor(
int corePoolSize,//執行緒池核心執行緒大小
int maximumPoolSize,//執行緒池最大執行緒數量
long keepAliveTime,//空閑執行緒存活時間
TimeUnit unit,//空閑執行緒存活時間單位,一共有七種靜態屬性(TimeUnit.DAYS天,TimeUnit.HOURS小時,TimeUnit.MINUTES分鐘,TimeUnit.SECONDS秒,TimeUnit.MILLISECONDS毫秒,TimeUnit.MICROSECONDS微妙,TimeUnit.NANOSECONDS納秒)
BlockingQueue<Runnable> workQueue,//工作隊列
ThreadFactory threadFactory,//執行緒工廠,主要用來創建執行緒(默認的工廠方法是:Executors.defaultThreadFactory()對執行緒進行安全檢查並命名)
RejectedExecutionHandler handler//拒絕策略(默認是:ThreadPoolExecutor.AbortPolicy不執行並拋出異常)
) 

使用示例:

ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 20, 2, TimeUnit.SECONDS, new LinkedBlockingQueue<>(5));

5.2.1、工作隊列

jdk 中提供了四種工作隊列:
①ArrayBlockingQueue
基於數組的有界阻塞隊列,按 FIFO 排序。新任務進來後,會放到該隊列的隊尾,有界的數組可以防止資源耗盡問題。當執行緒池中執行緒數量達到 corePoolSize 後,再有新任務進來,則會將任務放入該隊列的隊尾,等待被調度。如果隊列已經是滿的,則創建一個新執行緒,如果執行緒數量已經達到 maxPoolSize,則會執行拒絕策略。
②LinkedBlockingQuene
基於鏈表的無界阻塞隊列(其實最大容量為 Interger.MAX_VALUE),按照 FIFO 排序。由於該隊列的近似無界性,當執行緒池中執行緒數量達到 corePoolSize 後,再有新任務進來,會一直存入該隊列,而不會去創建新執行緒直到 maxPoolSize,因此使用該工作隊列時,參數 maxPoolSize 其實是不起作用的。
③SynchronousQuene
一個不快取任務的阻塞隊列,生產者放入一個任務必須等到消費者取出這個任務。也就是說新任務進來時,不會快取,而是直接被調度執行該任務,如果沒有可用執行緒,則創建新執行緒,如果執行緒數量達到 maxPoolSize,則執行拒絕策略。
④PriorityBlockingQueue
具有優先順序的無界阻塞隊列,優先順序通過參數 Comparator 實現。

5.2.2、拒絕策略

當工作隊列中的任務已到達最大限制,並且執行緒池中的執行緒數量也達到最大限制,這時如果有新任務提交進來,就會執行拒絕策略。jdk中提供了4中拒絕策略:
①ThreadPoolExecutor.CallerRunsPolicy
該策略下,在調用者執行緒中直接執行被拒絕任務的 run 方法,除非執行緒池已經 shutdown,則直接拋棄任務。
②ThreadPoolExecutor.AbortPolicy
該策略下,直接丟棄任務,並拋出 RejectedExecutionException 異常。
③ThreadPoolExecutor.DiscardPolicy
該策略下,直接丟棄任務,什麼都不做。
④ThreadPoolExecutor.DiscardOldestPolicy
該策略下,拋棄進入隊列最早的那個任務,然後嘗試把這次拒絕的任務放入隊列。
除此之外,還可以根據應用場景需要來實現 RejectedExecutionHandler 介面自定義策略。

6、執行緒池的關閉

  • shutdown():
    1、調用之後不允許繼續往執行緒池內添加執行緒;
    2、執行緒池的狀態變為 SHUTDOWN 狀態;
    3、所有在調用 shutdown() 方法之前提交到 ExecutorSrvice 的任務都會執行;
    4、一旦所有執行緒結束執行當前任務,ExecutorService 才會真正關閉。
  • shutdownNow():
    1、該方法返回尚未執行的 task 的 List;
    2、執行緒池的狀態變為 STOP 狀態;
    3、嘗試停止所有的正在執行或暫停任務的執行緒。
    簡單點來說,就是:
    shutdown() 調用後,不可以再 submit 新的 task,已經 submit 的將繼續執行
    shutdownNow() 調用後,試圖停止當前正在執行的 task,並返回尚未執行的 task 的 list

7、總結

本文簡單介紹了執行緒池的一些相關知識,相信大家對執行緒池的優點,執行緒池的生命周期,執行緒池的工作流程及執行緒池的使用有了一個大概的了解,也希望能對有需要的人提供一點幫助!文中有錯誤的地方,還請留言給予指正,謝謝~
也歡迎大家關注我的公眾號:Java的成神之路,免費領取最新面試資料,技術電子書,架構進階相關資料等。
在這裡插入圖片描述

Tags: