為什麼線程池不允許使用Executors去建立?
Executors 是一個Java中的工具類。提供工廠方法來建立不同類型的線程池。

Executors的建立線程池的方法,建立出來的線程池都實作了ExecutorService接口。常用方法有以下幾個:
//建立固定數目線程的線程池
ExecutorService executor1 = Executors.newFixedThreadPool(8);
//建立一個可緩存的線程池,調用execute 将重用以前構造的線程(如果線程可用)。如果沒有可用的線程,則建立一個
//新線程并添加到池中。終止并從緩存中移除那些已有 60 秒鐘未被使用的線程。
ExecutorService executor2 = Executors.newCachedThreadPool();
//建立一個單線程化的Executor
ExecutorService executor3 = Executors.newSingleThreadExecutor();
//建立一個支援定時及周期性的任務執行的線程池,多數情況下可用來替代Timer類
ScheduledExecutorService executor4 = Executors.newScheduledThreadPool(8);
這個類整體來說使用起來比較友善,但是為什麼說不建議用,下面來看阿裡社群Java開發規範中的強制限制:
針對阿裡Java開發手冊提到的OOM問題,先模拟一段程式
public class ExecutorsDemoController {
private static ExecutorService executor = Executors.newFixedThreadPool(8);
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 {
System.out.println(Thread.currentThread().getName());
Thread.sleep(1000000);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
通過指定JVM參數:-Xmx8m -Xms8m 運作以上代碼,會抛出OOM:
執行結果:
錯誤資訊:
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
at com.dongao.project.danotice.controller.ExecutorsDemoController.main(ExecutorsDemoController.java:17)
代碼中的第17行發生異常。
通過上面的報錯資訊可以看出,其中真正導緻OOM的是LinkedBlockingQueue.offer(E e),檢視Executors.newFixedThreadPool(int nThreads)的源碼,看到阻塞隊列用的是new LinkedBlockingQueue<Runnable>();
Java中的 BlockingQueue主要有兩種實作,分别是ArrayBlockingQueue 和 LinkedBlockingQueue。
ArrayBlockingQueue是一個用數組實作的有界阻塞隊列,必須設定容量。如圖
LinkedBlockingQueue是一個用連結清單實作的有界阻塞隊列,容量可以選擇進行設定,不設定的話,将是一個無邊界的阻塞隊列,最大長度為Integer.MAX_VALUE。如圖
這裡的問題就出在:不設定的話,将是一個無邊界的阻塞隊列,最大長度為Integer.MAX_VALUE。也就是說,如果我們不設定LinkedBlockingQueue的容量的話,其預設容量将會是Integer.MAX_VALUE。
而newFixedThreadPool中建立LinkedBlockingQueue時,并未指定容量。此時,LinkedBlockingQueue就是一個無邊界隊列,對于一個無邊界隊列來說,是可以不斷的向隊列中加入任務的,這種情況下就有可能因為任務過多而導緻記憶體溢出問題。
上面提到的問題除了我們測試用的newFixedThreadPool,還有newSingleThreadExecutor,但是并不是說newCachedThreadPool和newScheduledThreadPool就不會出問題,他們兩個建立的最大線程數可能是Integer.MAX_VALUE,而建立巨多的線程也有可能導緻OOM。
避免使用Executors建立線程主要是避免其中一些參數給的預設值,那麼可以直接用ThreadPoolExecutor建立線程,并且指定具體的參數值。
private static ExecutorService execute = new ThreadPoolExecutor(10,10,60L, TimeUnit.SECONDS,new LinkedBlockingQueue<>(8));
根據ThreadPoolExecutor構造函數中參數,具體參數說明如下:
corePoolSize:線程池中核心線程數的最大值
maximumPoolSize:線程池中能擁有最多線程數
keepAliveTime:表示空閑線程的存活時間
unit:表示keepAliveTime的機關
workQueue:用于緩存任務的阻塞隊列
此處還有一個預設參數:
handler:表示當 workQueue 已滿,且池中的線程數達到 maximumPoolSize 時,線程池拒絕添加新任務時采取的政策,預設如下:
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
表示:抛出RejectedExecutionException異常
或者也可以如下建立線程:
private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("pool-%d").build();
private static ExecutorService executor = new ThreadPoolExecutor(10,10,60L, TimeUnit.SECONDS,new LinkedBlockingQueue<>(8),namedThreadFactory);
通過上述方式建立線程時,不僅可以避免OOM的問題,還可以自定義線程名稱,更加友善的出錯的時候溯源。