天天看點

為什麼線程池不允許使用Executors去建立?

為什麼線程池不允許使用Executors去建立?

Executors 是一個Java中的工具類。提供工廠方法來建立不同類型的線程池。

為什麼線程池不允許使用Executors去建立?

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開發規範中的強制限制:

為什麼線程池不允許使用Executors去建立?

針對阿裡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:

執行結果:

為什麼線程池不允許使用Executors去建立?

錯誤資訊:

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)      
為什麼線程池不允許使用Executors去建立?

代碼中的第17行發生異常。

通過上面的報錯資訊可以看出,其中真正導緻OOM的是LinkedBlockingQueue.offer(E e),檢視Executors.newFixedThreadPool(int nThreads)的源碼,看到阻塞隊列用的是new LinkedBlockingQueue<Runnable>();

為什麼線程池不允許使用Executors去建立?

Java中的 BlockingQueue主要有兩種實作,分别是ArrayBlockingQueue 和 LinkedBlockingQueue。

ArrayBlockingQueue是一個用數組實作的有界阻塞隊列,必須設定容量。如圖

為什麼線程池不允許使用Executors去建立?

LinkedBlockingQueue是一個用連結清單實作的有界阻塞隊列,容量可以選擇進行設定,不設定的話,将是一個無邊界的阻塞隊列,最大長度為Integer.MAX_VALUE。如圖

為什麼線程池不允許使用Executors去建立?

這裡的問題就出在:不設定的話,将是一個無邊界的阻塞隊列,最大長度為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構造函數中參數,具體參數說明如下:

為什麼線程池不允許使用Executors去建立?

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的問題,還可以自定義線程名稱,更加友善的出錯的時候溯源。