[轉帖]JUC內置線程池

https://cloud.tencent.com/developer/article/2235750

 

ThreadPoolExecutor

ThreadPoolExecutor是最基礎的線程池類:

12345678

public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

  • corePoolSize 核心線程數目 (最多保留的線程數)
  • maximumPoolSize 最大線程數目
  • keepAliveTime 生存時間 - 針對救急線程
  • unit 時間單位 - 針對救急線程
  • workQueue 阻塞隊列
  • threadFactory 線程工廠 - 可以爲線程創建時起個好名字
  • handler 拒絕策略

工作原理

  1. 線程池中剛開始沒有線程,當一個任務提交給線程池後,線程池會創建一個新線程來執行任務。
  2. 當線程數達到 corePoolSize 並沒有線程空閒,這時再加入任務,新加的任務會被加入workQueue 隊列排隊,直到有空閒的線程。
  3. 如果隊列選擇了有界隊列,那麼任務超過了隊列大小時,會創建maximumPoolSize - corePoolSize數目的線程來救急。
  4. 如果線程到達maximumPoolSize仍然有新任務這時會執行拒絕策略。拒絕策略 jdk 提供了 4 種實現,其它著名框架也提供了實現:
    • AbortPolicy:讓調用者拋出RejectedExecutionException異常,這是默認策略;
    • CallerRunsPolicy:讓調用者運行任務;
    • DiscardPolicy:放棄本次任務;
    • DiscardOldestPolicy:放棄隊列中最早的任務,本任務取而代之;
    • Dubbo的實現,在拋出 RejectedExecutionException 異常之前會記錄日誌,並 dump 線程棧信息,方便定位問題;
    • Netty的實現,是創建一個新線程來執行任務;
    • ActiveMQ的實現,帶超時等待(60s)嘗試放入隊列,類似我們之前自定義的拒絕策略;
    • PinPoint的實現,它使用了一個拒絕策略鏈,會逐一嘗試策略鏈中每種拒絕策略;
  5. 當高峯過去後,超過corePoolSize的救急線程如果一段時間沒有任務做,需要結束節省資源,這個時間由keepAliveTimeunit來控制。

newFixedThreadPool

123

public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}

特點

  • 核心線程數 == 最大線程數(沒有救急線程被創建),因此也無需超時時間;
  • 阻塞隊列是無界的,可以放任意數量的任務。

評價

適用於任務量已知,相對耗時的任務。

newCachedThreadPool

123

public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}

特點

  • 核心線程數是0, 最大線程數是Integer.MAX_VALUE,救急線程的空閒生存時間是60s,意味着:
    • 全部都是救急線程(60s 後可以回收);
    • 救急線程可以無限創建;
  • 隊列採用了SynchronousQueue實現特點是,它沒有容量,沒有線程來取是放不進去的(一手交錢、一手交貨)。

評價

整個線程池表現爲線程數會根據任務量不斷增長,沒有上限,當任務執行完畢,空閒 1分鐘後釋放線程。 適合任務數比較密集,但每個任務執行時間較短的情況。

newSingleThreadExecutor

123

public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));}

使用場景: 希望多個任務排隊執行。線程數固定爲 1,任務數多於 1 時,會放入無界隊列排隊。任務執行完畢,這唯一的線程也不會被釋放。 區別:

  • 自己創建一個單線程串行執行任務,如果任務執行失敗而終止那麼沒有任何補救措施,而線程池還會新建一個線程,保證池的正常工作;
  • Executors.newSingleThreadExecutor()線程個數始終爲1,不能修改;
    • FinalizableDelegatedExecutorService應用的是裝飾器模式,只對外暴露了ExecutorService接口,因此不能調用 ThreadPoolExecutor中特有的方法。
  • Executors.newFixedThreadPool(1)初始時爲1,以後還可以修改;
    • 對外暴露的是ThreadPoolExecutor對象,可以強轉後調用setCorePoolSize等方法進行修改。

newScheduledThreadPool

newScheduledThreadPool屬於任務調度線程池:

12345678910

ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);// 添加兩個任務,希望它們都在 1s 後執行executor.schedule(() -> { System.out.println("任務1,執行時間:" + new Date()); try { Thread.sleep(2000); } catch (InterruptedException e) { }}, 1000, TimeUnit.MILLISECONDS);executor.schedule(() -> { System.out.println("任務2,執行時間:" + new Date());}, 1000, TimeUnit.MILLIS);

任務1並不會影響任務2的開始執行時間。

scheduleAtFixedRate

123456

ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);log.debug("start...");pool.scheduleAtFixedRate(() -> { log.debug("running..."); sleep(2);}, 1, 1, TimeUnit.SECONDS);

任務執行時間超過了間隔時間,間隔時間增大爲任務時間。 輸出分析:一開始,延時 1s,接下來,由於任務執行時間大於間隔時間,間隔被『撐』到了2s。

12345

21:44:30.311 c.TestTimer [main] - start...21:44:31.360 c.TestTimer [pool-1-thread-1] - running...21:44:33.361 c.TestTimer [pool-1-thread-1] - running...21:44:35.362 c.TestTimer [pool-1-thread-1] - running...21:44:37.362 c.TestTimer [pool-1-thread-1] - running...

scheduleWithFixedDelay

123456

ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);log.debug("start...");pool.scheduleWithFixedDelay(()-> { log.debug("running..."); sleep(2);}, 1, 1, TimeUnit.SECONDS);

任務執行時間超過了間隔時間,間隔時間增大爲任務時間加上間隔時間。 輸出分析:一開始,延時 1s,之後任務時間2秒加上固定間隔時間1秒,所以間隔都是 3s。

12345

21:40:55.078 c.TestTimer [main] - start...21:40:56.140 c.TestTimer [pool-1-thread-1] - running...21:40:59.143 c.TestTimer [pool-1-thread-1] - running...21:41:02.145 c.TestTimer [pool-1-thread-1] - running...21:41:05.147 c.TestTimer [pool-1-thread-1] - running...

線程池提交任務

1234567891011121314151617

// 執行任務void execute(Runnable command);// 提交任務 task,用返回值 Future 獲得任務執行結果<T> Future<T> submit(Callable<T> task);// 提交 tasks 中所有任務<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;// 提交 tasks 中所有任務,帶超時時間<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;// 提交 tasks 中所有任務,哪個任務先成功執行完畢,返回此任務執行結果,其它任務取消<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;// 提交 tasks 中所有任務,哪個任務先成功執行完畢,返回此任務執行結果,其它任務取消,帶超時時間<T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;

關閉線程池

123456789101112131415161718192021222324

/*線程池狀態變爲 SHUTDOWN- 不會接收新任務- 但已提交任務會執行完- 此方法不會阻塞調用線程的執行*/void shutdown();/*線程池狀態變爲 STOP- 不會接收新任務- 會將隊列中的任務返回- 並用 interrupt 的方式中斷正在執行的任務*/List<Runnable> shutdownNow();// 不在 RUNNING 狀態的線程池,此方法就返回 trueboolean isShutdown();// 線程池狀態是否是 TERMINATEDboolean isTerminated();// 調用 shutdown 後,由於調用線程並不會等待所有任務運行結束,因此如果它想在線程池 TERMINATED 後做些事情,可以利用此方法等待boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;

本文參與 騰訊雲自媒體分享計劃,分享自作者個人站點/博客。
原始發表:2022-01-29,如有侵權請聯繫 [email protected] 刪除
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章