啟用Executor初始化線程池


前言

上文我們介紹了JDK中的線程池框架Executor。我們知道,只要需要創建線程的情況下,即使是在單線程模式下,我們也要盡量使用Executor。即:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1); //此處不該利用Executors工具類來初始化線程池

但是,在《阿里巴巴Java開發手冊》中有一條

【強制】線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。

Executors 返回的線程池對象的弊端如下:
FixedThreadPool 和 SingleThreadPool : 允許的請求隊列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。
CachedThreadPool 和 ScheduledThreadPool : 允許的創建線程數量為 Integer.MAX_VALUE,可能會創建大量的線程,從而導致 OOM。

可以看到,這是一個強制性的規則,並且是不允許使用Executors來創建,建議使用ThreadPoolExecutor來創建線程池,那我們先來回顧一下ExecutorsThreadPoolExecutor

我們可以看到ThreadPoolExecutor已經是Executor的具體實現了,而且具有較多可配參數(可配參數見下方,可僅了解,用到時再進行詳細查詢)。Executors是一個創建線程池的工具類,查看其源碼的話也會發現這幾種創建線程池的方法也都是通過調用ThreadPoolExecutor來實現的。

ThreadPoolExecutor一共有四個構造函數,七個可配參數,分別是

  1. corePoolSize: 線程池中保持存活線程的數量。
  2. maximumPoolSize: 線程池中允許線程數量的最大值
  3. keepAliveTime: 表示線程沒有任務執行時最多保持多久時間會終止
  4. unit: 參數keepAliveTime的時間單位
  5. workQueue: 一個阻塞隊列,用來存儲等待執行的任務
  6. threadFactory: 線程工廠,主要用來創建線程
  7. handler:表示當拒絕處理任務時的策略

分析

那么Executors到底會導致什么問題,才會讓開發手冊中直接被定義為不允許了呢。首先就是一個血淋淋的教訓,直接導致線上服務不可用,已經可以算是事故了。

實驗

我們也可以現在我們本地進行一下小實驗:

public class ExecutorsTesting { private static ExecutorService executor = Executors.newFixedThreadPool(15); public static void main(String[] args) { for (int i = 0; i < Integer.MAX_VALUE; i++) { executor.execute(new SubThread()); } } } class SubThread implements Runnable { @Override public void run() { try { Thread.sleep(10000); } catch (InterruptedException e) { //do nothing } } }

運行時指定JVM參數:-Xmx8m -Xms8m,大概幾秒鍾之后,會報出OOM錯誤:

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded at com.kaikeba.mybatis.ExecutorsTesting.main(ExecutorsTesting.java:10) //報錯行數為上述代碼中的executor.execute(new SubThread());

那么為什么會報出這個錯誤呢。

源碼分析

我們先來看一下Executors中的FixedThreadPool是如何構造的。

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

可以看到對於存儲等待執行的任務,FixedThreadPool是通過LinkedBlockingQueue來實現的。而我們知道LinkedBlockingQueue是一個鏈表實現的阻塞隊列,而如果不設置其容量的話,將會是一個無邊界的阻塞隊列,最大長度為Integer.MAX_VALUE由於Executors中並未設置容量,所以應用可以不斷向隊列中添加任務,導致OOM錯誤

上面提到的問題主要體現在newFixedThreadPoolnewSingleThreadExecutor兩個工廠方法上,並不是說newCachedThreadPoolnewScheduledThreadPool這兩個方法就安全了,這兩種方式創建的最大線程數可能是Integer.MAX_VALUE,而創建這么多線程,必然就有可能導致OOM。

如何該利用ThreadPoolExecutor來創建線程池呢?

我們其實可以看到Executors中的newFixedThreadPool其實也是調用ThreadPoolExecutor來實現的。正如手冊中所說,當我們不用Executors默認創建線程池的方法,而直接自己手動去調用ThreadPoolExecutor,可以讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。比如我們在Executors.newFixedThreadPool基礎上給LinkedBlockingQueue加一個容量,當隊列已經滿了,而仍需要添加新的請求會拋出相應異常,我們可以根據異常做相應處理。

public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(10)); //添加容量大小 }

除了自己定義ThreadPoolExecutor外。還可以利用其它開源類庫,如apache和guava等,可以有更多個性化配置。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM