線程池(ThreadPool)是一種基於池化思想管理和使用線程的機制。它是將多個線程預先存儲在一個「池子」內,當有任務出現時能夠避免從新建立和銷燬線程所帶來性能開銷,只須要從「池子」內取出相應的線程執行對應的任務便可。ios
池化思想在計算機的應用也比較普遍,好比如下這些:數據庫
線程池的優點主要體如今如下 4 點:數組
同時阿里巴巴在其《Java開發手冊》中也強制規定:線程資源必須經過線程池提供,不容許在應用中自行顯式建立線程。緩存
說明:線程池的好處是減小在建立和銷燬線程上所消耗的時間以及系統資源的開銷,解決資源不足的問題。若是不使用線程池,有可能形成系統建立大量同類線程而致使消耗完內存或者「過分切換」的問題。安全
知道了什麼是線程池以及爲什要用線程池以後,咱們再來看怎麼用線程池。併發
線程池的建立方法總共有 7 種,但整體來講可分爲 2 類:app
線程池的建立方式總共包含如下 7 種(其中 6 種是經過 Executors 建立的,1 種是經過ThreadPoolExecutor 建立的)ide
單線程池的意義從以上代碼能夠看出 newSingleThreadExecutor 和 newSingleThreadScheduledExecutor 建立的都是單線程池,那麼單線程池的意義是什麼呢?答:雖然是單線程池,但提供了工做隊列,生命週期管理,工做線程維護等功能。性能
建立一個固定大小的線程池,可控制併發的線程數,超出的線程會在隊列中等待。學習
public static void fixedThreadPool() { // 建立 2 個數據級的線程池 ExecutorService threadPool = Executors.newFixedThreadPool(2); // 建立任務 Runnable runnable = new Runnable() { @Override public void run() { System.out.println("任務被執行,線程:" + Thread.currentThread().getName()); } }; // 線程池執行任務(一次添加 4 個任務) // 執行任務的方法有兩種:submit 和 execute threadPool.submit(runnable); // 執行方式 1:submit threadPool.execute(runnable); // 執行方式 2:execute threadPool.execute(runnable); threadPool.execute(runnable); }
若是以爲以上方法比較繁瑣,還用更簡單的使用方法,以下代碼所示:
public static void fixedThreadPool() { // 建立線程池 ExecutorService threadPool = Executors.newFixedThreadPool(2); // 執行任務 threadPool.execute(() -> { System.out.println("任務被執行,線程:" + Thread.currentThread().getName()); }); }
建立一個可緩存的線程池,若線程數超過處理所需,緩存一段時間後會回收,若線程數不夠,則新建線程。
使用示例以下:
public static void cachedThreadPool() { // 建立線程池 ExecutorService threadPool = Executors.newCachedThreadPool(); // 執行任務 for (int i = 0; i < 10; i++) { threadPool.execute(() -> { System.out.println("任務被執行,線程:" + Thread.currentThread().getName()); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } }); } }
從上述結果能夠看出,線程池建立了 10 個線程來執行相應的任務。
建立單個線程數的線程池,它能夠保證先進先出的執行順序。
使用示例以下:
public static void singleThreadExecutor() { // 建立線程池 ExecutorService threadPool = Executors.newSingleThreadExecutor(); // 執行任務 for (int i = 0; i < 10; i++) { final int index = i; threadPool.execute(() -> { System.out.println(index + ":任務被執行"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } }); } }
建立一個能夠執行延遲任務的線程池。
使用示例以下
public static void scheduledThreadPool() { // 建立線程池 ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5); // 添加定時執行任務(1s 後執行) System.out.println("添加任務,時間:" + new Date()); threadPool.schedule(() -> { System.out.println("任務被執行,時間:" + new Date()); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } }, 1, TimeUnit.SECONDS); }
從上述結果能夠看出,任務在 1 秒以後被執行了,符合咱們的預期。
建立一個單線程的能夠執行延遲任務的線程池。
使用示例以下:
public static void SingleThreadScheduledExecutor() { // 建立線程池 ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor(); // 添加定時執行任務(2s 後執行) System.out.println("添加任務,時間:" + new Date()); threadPool.schedule(() -> { System.out.println("任務被執行,時間:" + new Date()); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } }, 2, TimeUnit.SECONDS); }
建立一個搶佔式執行的線程池(任務執行順序不肯定),注意此方法只有在 JDK 1.8+ 版本中才能使用。
使用示例以下:
public static void workStealingPool() { // 建立線程池 ExecutorService threadPool = Executors.newWorkStealingPool(); // 執行任務 for (int i = 0; i < 10; i++) { final int index = i; threadPool.execute(() -> { System.out.println(index + " 被執行,線程名:" + Thread.currentThread().getName()); }); } // 確保任務執行完成 while (!threadPool.isTerminated()) { } }
從上述結果能夠看出,任務的執行順序是不肯定的,由於它是搶佔式執行的。
最原始的建立線程池的方式,它包含了 7 個參數可供設置。
使用示例以下:
7.ThreadPoolExecutor 最原始的建立線程池的方式,它包含了 7 個參數可供設置。 使用示例以下: public static void myThreadPoolExecutor() { // 建立線程池 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10)); // 執行任務 for (int i = 0; i < 10; i++) { final int index = i; threadPool.execute(() -> { System.out.println(index + " 被執行,線程名:" + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); } }
ThreadPoolExecutor 最多能夠設置 7 個參數,以下代碼所示:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { // 省略... }
7 個參數表明的含義以下:
核心線程數,線程池中始終存活的線程數。
最大線程數,線程池中容許的最大線程數,當線程池的任務隊列滿了以後能夠建立的最大線程數。
最大線程數能夠存活的時間,當線程中沒有任務執行時,最大線程就會銷燬一部分,最終保持核心線程數量的線程。
單位是和參數 3 存活時間配合使用的,合在一塊兒用於設定線程的存活時間 ,參數 keepAliveTime 的時間單位有如下 7 種可選:
一個阻塞隊列,用來存儲線程池等待執行的任務,均爲線程安全,它包含如下 7 種類型:
較經常使用的是 LinkedBlockingQueue 和 Synchronous,線程池的排隊策略與 BlockingQueue 有關。
線程工廠,主要用來建立線程,默認爲正常優先級、非守護線程。
拒絕策略,拒絕處理任務時的策略,系統提供了 4 種可選:
默認策略爲 AbortPolicy。
ThreadPoolExecutor 關鍵節點的執行流程以下:
線程池的執行流程以下圖所示:
咱們來演示一下 ThreadPoolExecutor 的拒絕策略的觸發,咱們使用 DiscardPolicy 的拒絕策略,它會忽略並拋棄當前任務的策略,實現代碼以下:
public static void main(String[] args) { // 任務的具體方法 Runnable runnable = new Runnable() { @Override public void run() { System.out.println("當前任務被執行,執行時間:" + new Date() + " 執行線程:" + Thread.currentThread().getName()); try { // 等待 1s TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } }; // 建立線程,線程的任務隊列的長度爲 1 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1, 100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1), new ThreadPoolExecutor.DiscardPolicy()); // 添加並執行 4 個任務 threadPool.execute(runnable); threadPool.execute(runnable); threadPool.execute(runnable); threadPool.execute(runnable); }
咱們建立了一個核心線程數和最大線程數都爲 1 的線程池,而且給線程池的任務隊列設置爲 1,這樣當咱們有 2 個以上的任務時就會觸發拒絕策略,執行的結果以下圖所示
除了 Java 自身提供的 4 種拒絕策略以外,咱們也能夠自定義拒絕策略,示例代碼以下:
public static void main(String[] args) { // 任務的具體方法 Runnable runnable = new Runnable() { @Override public void run() { System.out.println("當前任務被執行,執行時間:" + new Date() + " 執行線程:" + Thread.currentThread().getName()); try { // 等待 1s TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } }; // 建立線程,線程的任務隊列的長度爲 1 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1, 100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1), new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { // 執行自定義拒絕策略的相關操做 System.out.println("我是自定義拒絕策略~"); } }); // 添加並執行 4 個任務 threadPool.execute(runnable); threadPool.execute(runnable); threadPool.execute(runnable); threadPool.execute(runnable); }
通過以上的學習咱們對整個線程池也有了必定的認識了,那究竟該如何選擇線程池呢?
咱們來看下阿里巴巴《Java開發手冊》給咱們的答案:
【強制】線程池不容許使用 Executors 去建立,而是經過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同窗更加明確線程池的運行規則,規避資源耗盡的風險。
說明:Executors 返回的線程池對象的弊端以下:
1) FixedThreadPool 和 SingleThreadPool:容許的請求隊列長度爲 Integer.MAX_VALUE,可能會堆積大量的請求,從而致使 OOM。
2)CachedThreadPool:容許的建立線程數量爲 Integer.MAX_VALUE,可能會建立大量的線程,從而致使 OOM。
因此綜上狀況所述,咱們推薦使用 ThreadPoolExecutor 的方式進行線程池的建立,由於這種建立方式更可控,而且更加明確了線程池的運行規則,能夠規避一些未知的風險。
本文咱們介紹了線程池的 7 種建立方式,其中最推薦使用的是 ThreadPoolExecutor 的方式進行線程池的建立,ThreadPoolExecutor 最多能夠設置 7 個參數,固然設置 5 個參數也能夠正常使用,ThreadPoolExecutor 當任務過多(處理不過來)時提供了 4 種拒絕策略,固然咱們也能夠自定義拒絕策略,但願本文的內容能幫助到你。
注:
參考:
https://www.toutiao.com/i6907090760416510475/?tt_from=weixin&utm_campaign=client_share&wxshare_count=1×tamp=1608252194&app=news_article&utm_source=weixin&utm_medium=toutiao_ios&use_new_style=1&req_id=202012180843140100260772113E3F1159&=&=&=&=&=&=&=&=&=&group_id=6907090760416510475