經過這六點,瞭解Java線程池的全面(總結上篇)

目錄數據庫

  • 一 使用線程池的好處
  • 二 Executor 框架
  • 三 (重要)ThreadPoolExecutor 類簡單介紹
  • 四 幾種常見的線程池詳解
  • 五 (重要)ThreadPoolExecutor 使用示例
  • 六 ScheduledThreadPoolExecutor 詳解
  • 一 使用線程池的好處編程

    池化技術相比你們已經家常便飯了,線程池、數據庫鏈接池、Http 鏈接池等等都是對這個思想的應用。池化技術的思想主要是爲了減小每次獲取資源的消耗,提升對資源的利用率。bash

    線程池提供了一種限制和管理資源(包括執行一個任務)。 每一個線程池還維護一些基本統計信息,例如已完成任務的數量。併發

    這裏借用《Java 併發編程的藝術》提到的來講一下使用線程池的好處框架

    • 下降資源消耗。經過重複利用已建立的線程下降線程建立和銷燬形成的消耗。
    • 提升響應速度。當任務到達時,任務能夠不須要的等到線程建立就能當即執行。
    • 提升線程的可管理性。線程是稀缺資源,若是無限制的建立,不只會消耗系統資源,還會下降系統的穩定性,使用線程池能夠進行統一的分配,調優和監控。

    二 Executor 框架異步

    2.1 簡介函數

    Executor 框架是 Java5 以後引進的,在 Java 5 以後,經過 Executor 來啓動線程比使用 Thread 的 start 方法更好,除了更易管理,效率更好(用線程池實現,節約開銷)外,還有關鍵的一點:有助於避免 this 逃逸問題。工具

    補充:this 逃逸是指在構造函數返回以前其餘線程就持有該對象的引用. 調用還沒有構造徹底的對象的方法可能引起使人疑惑的錯誤。性能

    Executor 框架不只包括了線程池的管理,還提供了線程工廠、隊列以及拒絕策略等,Executor 框架讓併發編程變得更加簡單。ui

    2.2 Executor 框架結構(主要由三大部分組成)

    1) 任務(Runnable /Callable)

    執行任務須要實現的 Runnable 接口Callable接口Runnable 接口Callable 接口 實現類均可以被 ThreadPoolExecutorScheduledThreadPoolExecutor 執行。

    2) 任務的執行(Executor)

    以下圖所示,包括任務執行機制的核心接口 Executor ,以及繼承自 Executor 接口的 ExecutorService 接口。ThreadPoolExecutorScheduledThreadPoolExecutor 這兩個關鍵類實現了 ExecutorService 接口

    這裏提了不少底層的類關係,可是,實際上咱們須要更多關注的是 ThreadPoolExecutor 這個類,這個類在咱們實際使用線程池的過程當中,使用頻率仍是很是高的。

    注意: 經過查看 ScheduledThreadPoolExecutor 源代碼咱們發現 ScheduledThreadPoolExecutor 其實是繼承了 ThreadPoolExecutor 並實現了 ScheduledExecutorService ,而 ScheduledExecutorService 又實現了 ExecutorService,正如咱們下面給出的類關係圖顯示的同樣。

    ThreadPoolExecutor 類描述:

    //AbstractExecutorService實現了ExecutorService接口public class ThreadPoolExecutor extends AbstractExecutorService複製代碼

    ScheduledThreadPoolExecutor 類描述:

    //ScheduledExecutorService實現了ExecutorService接口public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService複製代碼
    經過這六點,瞭解Java線程池的全面(總結上篇)

    3) 異步計算的結果(Future)

    Future 接口以及 Future 接口的實現類 FutureTask 類均可以表明異步計算的結果。

    當咱們把 Runnable接口Callable 接口 的實現類提交給 ThreadPoolExecutorScheduledThreadPoolExecutor 執行。(調用 submit() 方法時會返回一個 FutureTask對象)

    2.3 Executor 框架的使用示意圖

    經過這六點,瞭解Java線程池的全面(總結上篇)

    1. 主線程首先要建立實現 Runnable 或者 Callable 接口的任務對象。
    2. 把建立完成的實現 Runnable/Callable接口的 對象直接交給 ExecutorService 執行: ExecutorService.execute(Runnable command))或者也能夠把 Runnable 對象或Callable 對象提交給 ExecutorService 執行(ExecutorService.submit(Runnable task)或 ExecutorService.submit(Callable <T> task))。
    3. 若是執行 ExecutorService.submit(…),ExecutorService 將返回一個實現Future接口的對象(咱們剛剛也提到過了執行 execute()方法和 submit()方法的區別,submit()會返回一個 FutureTask 對象)。因爲 FutureTask 實現了 Runnable,咱們也能夠建立 FutureTask,而後直接交給 ExecutorService 執行。
    4. 最後,主線程能夠執行 FutureTask.get()方法來等待任務執行完成。主線程也能夠執行 FutureTask.cancel(boolean mayInterruptIfRunning)來取消此任務的執行。

    三 (重要)ThreadPoolExecutor 類簡單介紹

    線程池實現類 ThreadPoolExecutor 是 Executor 框架最核心的類。

    3.1 ThreadPoolExecutor 類分析

    ThreadPoolExecutor 類中提供的四個構造方法。咱們來看最長的那個,其他三個都是在這個構造方法的基礎上產生(其餘幾個構造方法說白點都是給定某些默認參數的構造方法好比默認制定拒絕策略是什麼),這裏就不貼代碼講了,比較簡單。

    /** * 用給定的初始參數建立一個新的ThreadPoolExecutor。 */ public ThreadPoolExecutor(int corePoolSize,//線程池的核心線程數量 int maximumPoolSize,//線程池的最大線程數 long keepAliveTime,//當線程數大於核心線程數時,多餘的空閒線程存活的最長時間 TimeUnit unit,//時間單位 BlockingQueue<Runnable> workQueue,//任務隊列,用來儲存等待執行任務的隊列 ThreadFactory threadFactory,//線程工廠,用來建立線程,通常默認便可 RejectedExecutionHandler handler//拒絕策略,當提交的任務過多而不能及時處理時,咱們能夠定製策略來處理任務 ) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }複製代碼

    下面這些對建立 很是重要,在後面使用線程池的過程當中你必定會用到!因此,務必拿着小本本記清楚。

    ThreadPoolExecutor 3 個最重要的參數:

    • corePoolSize : 核心線程數線程數定義了最小能夠同時運行的線程數量。
    • maximumPoolSize : 當隊列中存放的任務達到隊列容量的時候,當前能夠同時運行的線程數量變爲最大線程數。
    • workQueue: 當新任務來的時候會先判斷當前運行的線程數量是否達到核心線程數,若是達到的話,信任就會被存放在隊列中。

    ThreadPoolExecutor其餘常見參數:

    1. keepAliveTime:當線程池中的線程數量大於 corePoolSize 的時候,若是這時沒有新的任務提交,核心線程外的線程不會當即銷燬,而是會等待,直到等待的時間超過了 keepAliveTime纔會被回收銷燬;
    2. unit : keepAliveTime 參數的時間單位。
    3. threadFactory :executor 建立新線程的時候會用到。
    4. handler :飽和策略。關於飽和策略下面單獨介紹一下。

    下面這張圖能夠加深你對線程池中各個參數的相互關係的理解(圖片來源:《Java性能調優實戰》):

    經過這六點,瞭解Java線程池的全面(總結上篇)

    ThreadPoolExecutor 飽和策略定義:

    若是當前同時運行的線程數量達到最大線程數量而且隊列也已經被放滿了任時,ThreadPoolTaskExecutor 定義一些策略:

    • ThreadPoolExecutor.AbortPolicy:拋出 RejectedExecutionException來拒絕新任務的處理。
    • ThreadPoolExecutor.CallerRunsPolicy:調用執行本身的線程運行任務。您不會任務請求。可是這種策略會下降對於新任務提交速度,影響程序的總體性能。另外,這個策略喜歡增長隊列容量。若是您的應用程序能夠承受此延遲而且你不能任務丟棄任何一個任務請求的話,你能夠選擇這個策略。
    • ThreadPoolExecutor.DiscardPolicy: 不處理新任務,直接丟棄掉。
    • ThreadPoolExecutor.DiscardOldestPolicy: 此策略將丟棄最先的未處理的任務請求。

    舉個例子:

    Spring 經過 ThreadPoolTaskExecutor 或者咱們直接經過 ThreadPoolExecutor 的構造函數建立線程池的時候,當咱們不指定 RejectedExecutionHandler 飽和策略的話來配置線程池的時候默認使用的是 ThreadPoolExecutor.AbortPolicy。在默認狀況下,ThreadPoolExecutor 將拋出 RejectedExecutionException 來拒絕新來的任務 ,這表明你將丟失對這個任務的處理。 對於可伸縮的應用程序,建議使用 ThreadPoolExecutor.CallerRunsPolicy。當最大池被填滿時,此策略爲咱們提供可伸縮隊列。(這個直接查看 ThreadPoolExecutor 的構造函數源碼就能夠看出,比較簡單的緣由,這裏就不貼代碼了。)

    3.2 推薦使用 ThreadPoolExecutor 構造函數建立線程池

    在《阿里巴巴 Java 開發手冊》「併發處理」這一章節,明確指出線程資源必須經過線程池提供,不容許在應用中自行顯示建立線程。

    爲何呢?

    使用線程池的好處是減小在建立和銷燬線程上所消耗的時間以及系統資源開銷,解決資源不足的問題。若是不使用線程池,有可能會形成系統建立大量同類線程而致使消耗完內存或者「過分切換」的問題。

    另外《阿里巴巴 Java 開發手冊》中強制線程池不容許使用 Executors 去建立,而是經過 ThreadPoolExecutor 構造函數的方式,這樣的處理方式讓寫的同窗更加明確線程池的運行規則,規避資源耗盡的風險

    Executors 返回線程池對象的弊端以下:

    FixedThreadPool 和 SingleThreadExecutor : 容許請求的隊列長度爲 Integer.MAX_VALUE,可能堆積大量的請求,從而致使 OOM。CachedThreadPool 和 ScheduledThreadPool : 容許建立的線程數量爲 Integer.MAX_VALUE ,可能會建立大量線程,從而致使 OOM。

    方式一:經過ThreadPoolExecutor構造函數實現(推薦)

    經過這六點,瞭解Java線程池的全面(總結上篇)

    方式二:經過 Executor 框架的工具類 Executors 來實現 咱們能夠建立三種類型的 ThreadPoolExecutor:

    • FixedThreadPool
    • SingleThreadExecutor
    • CachedThreadPool

    對應 Executors 工具類中的方法如圖所示:

    經過這六點,瞭解Java線程池的全面(總結上篇)

    四. 幾種常見的線程池詳解

    4.1 FixedThreadPool

    4.1.1 介紹

    FixedThreadPool 被稱爲可重用固定線程數的線程池。經過 Executors 類中的相關源代碼來看一下相關實現:

    /** * 建立一個可重用固定數量線程的線程池 */ public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory); }複製代碼

    另外還有一個 FixedThreadPool 的實現方法,和上面的相似,因此這裏很少作闡述:

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

    從上面源代碼能夠看出新建立的 FixedThreadPool 的 corePoolSize 和 maximumPoolSize 都被設置爲 nThreads,這個 nThreads 參數是咱們使用的時候本身傳遞的。

    4.1.2 執行任務過程介紹

    FixedThreadPool 的 execute() 方法運行示意圖:

    經過這六點,瞭解Java線程池的全面(總結上篇)

    上圖說明:

    1. 若是當前運行的線程數小於 corePoolSize, 若是再來新任務的話,就建立新的線程來執行任務;
    2. 當前運行的線程數等於 corePoolSize 後, 若是再來新任務的話,會將任務加入 LinkedBlockingQueue;
    3. 線程池中的線程執行完 手頭的任務後,會在循環中反覆從 LinkedBlockingQueue 中獲取任務來執行;

    4.1.3 爲何不推薦使用FixedThreadPool?

    FixedThreadPool 使用無界隊列 LinkedBlockingQueue(隊列的容量爲 Intger.MAX_VALUE)做爲線程池的工做隊列會對線程池帶來以下影響 :

    1. 當線程池中的線程數達到 corePoolSize 後,新任務將在無界隊列中等待,所以線程池中的線程數不會超過 corePoolSize;
    2. 因爲使用無界隊列時 maximumPoolSize 將是一個無效參數,由於不可能存在任務隊列滿的狀況。因此,經過建立 FixedThreadPool的源碼能夠看出建立的 FixedThreadPool 的 corePoolSize 和 maximumPoolSize 被設置爲同一個值。
    3. 因爲 1 和 2,使用無界隊列時 keepAliveTime 將是一個無效參數;
    4. 運行中的 FixedThreadPool(未執行 shutdown()或 shutdownNow())不會拒絕任務,在任務比較多的時候會致使 OOM(內存溢出)。

    4.2 SingleThreadExecutor 詳解

    4.2.1 介紹

    SingleThreadExecutor 是隻有一個線程的線程池。下面看看SingleThreadExecutor 的實現:

    /** *返回只有一個線程的線程池 */ public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory)); }複製代碼
    public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }複製代碼

    從上面源代碼能夠看出新建立的 SingleThreadExecutor 的 corePoolSize 和 maximumPoolSize 都被設置爲 1.其餘參數和 FixedThreadPool 相同。

    4.2.2 執行任務過程介紹

    SingleThreadExecutor 的運行示意圖:

    經過這六點,瞭解Java線程池的全面(總結上篇)

    上圖說明;

    1. 若是當前運行的線程數少於 corePoolSize,則建立一個新的線程執行任務;
    2. 當前線程池中有一個運行的線程後,將任務加入 LinkedBlockingQueue
    3. 線程執行完當前的任務後,會在循環中反覆從 LinkedBlockingQueue 中獲取任務來執行;

    4.2.3 爲何不推薦使用FixedThreadPool?

    SingleThreadExecutor 使用無界隊列 LinkedBlockingQueue 做爲線程池的工做隊列(隊列的容量爲 Intger.MAX_VALUE)。SingleThreadExecutor 使用無界隊列做爲線程池的工做隊列會對線程池帶來的影響與 FixedThreadPool 相同。說簡單點就是可能會致使 OOM,

    4.3 CachedThreadPool 詳解

    4.3.1 介紹

    CachedThreadPool 是一個會根據須要建立新線程的線程池。下面經過源碼來看看 CachedThreadPool 的實現:

    /** * 建立一個線程池,根據須要建立新線程,但會在先前構建的線程可用時重用它。 */ public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory); }複製代碼
    public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }複製代碼

    CachedThreadPool 的 corePoolSize 被設置爲空(0),maximumPoolSize 被設置爲 Integer.MAX.VALUE,即它是無界的,這也就意味着若是主線程提交任務的速度高於 maximumPool 中線程處理任務的速度時,CachedThreadPool 會不斷建立新的線程。極端狀況下,這樣會致使耗盡 cpu 和內存資源。

    4.3.2 執行任務過程介紹

    CachedThreadPool 的 execute()方法的執行示意圖:

    經過這六點,瞭解Java線程池的全面(總結上篇)

    上圖說明:

    1. 首先執行 SynchronousQueue.offer(Runnable task) 提交任務到任務隊列。若是當前 maximumPool 中有閒線程正在執行 SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那麼主線程執行 offer 操做與空閒線程執行的 poll 操做配對成功,主線程把任務交給空閒線程執行,execute()方法執行完成,不然執行下面的步驟 2;
    2. 當初始 maximumPool 爲空,或者 maximumPool 中沒有空閒線程時,將沒有線程執行 SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。這種狀況下,步驟 1 將失敗,此時 CachedThreadPool會建立新線程執行任務,execute 方法執行完成;

    4.3.3 爲何不推薦使用CachedThreadPool?

    CachedThreadPool容許建立的線程數量爲 Integer.MAX_VALUE ,可能會建立大量線程,從而致使 OOM。

    記得關注(下篇)已經給你們準備好了

    歡迎你們點贊關注轉發一塊兒來討論。會天天給你們帶來一到兩個知識點,一塊兒成長。

    經過這六點,瞭解Java線程池的全面(總結上篇)
    相關文章
    相關標籤/搜索