天天看點

阿裡巴巴:禁止使用 Executors 建立線程池!為什麼啊?

Executor是不建議的

Executors類為我們提供了各種類型的線程池,經常使用的工廠方法有:

public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newCachedThreadPool()
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)      

書寫一段很簡單的測試代碼:

public class ThreadDemo {
 public static void main(String[] args) {
  ExecutorService es = Executors.newSingleThreadExecutor();
 }
}      

當我們用阿裡巴巴的P3C檢查代碼時,會被教育的!!!!

阿裡巴巴:禁止使用 Executors 建立線程池!為什麼啊?

阿裡爸爸是不允許這麼建立線程池的,上面的警告寫的很明确“線程池不允許使用Executors去建立,而是通過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學更加明确線程池的運作規則,規避資源耗盡的風險。”(PS:很難得在編譯器中看到中文提示,對于英語不好的同學來說,簡直是福音,喜極而泣!!!!)

強制使用ThreadPoolExecutor

我們使用ThreadPoolExecutor建立線程池:

public class ThreadDemo {
 public static void main(String[] args) {
  ExecutorService es = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<Runnable>(10), Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.DiscardPolicy());
 }
}      

此時,再用P3C檢查代碼,終于沒有報錯了。

在華麗的分隔符之後,我們還是有必要從JDK源碼的層面深挖一下其中的原理。

首先是靜态方法​

​newSingleThreadExecutor()​

​​、​

​newFixedThreadPool(int nThreads)​

​​、​

​newCachedThreadPool()​

​。我們來看一下其源碼實作(基于JDK8)。

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
}

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

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}      

通過檢視源碼我們知道上述三種靜态方法的内部實作均使用了​

​ThreadPoolExecutor​

​​類。難怪阿裡爸爸會建議通過​

​ThreadPoolExecutor​

​的方式實作,原來Executors類的靜态方法也是用的它,隻不過幫我們配了一些參數而已。

第二是​

​ThreadPoolExecutor​

​​類的構造方法。既然現在要直接使用​

​ThreadPoolExecutor​

​類了,那麼其中的初始化參數就要我們自己配了,了解其構造方法勢在必行。

​ThreadPoolExecutor​

​類一共有四個構造方法,我們隻需要了解之中的一個就可以了,因為其他三種構造方法隻是幫我們配置了一些預設參數,最後還是調用了它。

public ThreadPoolExecutor(int corePoolSize,
            int maximumPoolSize,
            long keepAliveTime,
            TimeUnit unit,
            BlockingQueue<Runnable> workQueue,
            ThreadFactory threadFactory,
            RejectedExecutionHandler handler)      

其中的參數含義是:

  • ​corePoolSize​

    ​:線程池中的線程數量;
  • ​maximumPoolSize​

    ​:線程池中的最大線程數量;
  • ​keepAliveTime​

    ​:當線程池線程數量超過corePoolSize時,多餘的空閑線程會在多長時間内被銷毀;
  • ​unit​

    ​:keepAliveTime的時間機關;
  • ​workQueue​

    ​:任務隊列,被送出但是尚未被執行的任務;
  • ​threadFactory​

    ​​:線程工廠,用于建立線程,一般情況下使用預設的,即Executors類的靜态方法defaultThreadFactory();​

    ​handler​

    ​:拒絕政策。當任務太多來不及處理時,如何拒絕任務。

對于這些參數要有以下了解:

corePoolSize與maximumPoolSize的關系

首先corePoolSize肯定是 <= maximumPoolSize。

其他關系如下:

  • 若目前線程池中線程數 < corePoolSize,則每來一個任務就建立一個線程去執行;
  • 若目前線程池中線程數 >= corePoolSize,會嘗試将任務添加到任務隊列。如果添加成功,則任務會等待空閑線程将其取出并執行;
  • 若隊列已滿,且目前線程池中線程數 < maximumPoolSize,建立新的線程;
  • 若目前線程池中線程數 >= maximumPoolSize,則會采用拒絕政策(JDK提供了四種,下面會介紹到)。
注意:關系3是針對的有界隊列,無界隊列永遠都不會滿,是以隻有前2種關系。

workQueue

參數workQueue是指送出但未執行的任務隊列。若目前線程池中線程數>=corePoolSize時,就會嘗試将任務添加到任務隊列中。主要有以下幾種:

  • ​SynchronousQueue​

    ​:直接送出隊列。SynchronousQueue沒有容量,是以實際上送出的任務不會被添加到任務隊列,總是将新任務送出給線程執行,如果沒有空閑的線程,則嘗試建立新的線程,如果線程數量已經達到最大值(maximumPoolSize),則執行拒絕政策。
  • ​LinkedBlockingQueue​

    ​:無界的任務隊列。當有新的任務來到時,若系統的線程數小于corePoolSize,線程池會建立新的線程執行任務;當系統的線程數量等于corePoolSize後,因為是無界的任務隊列,總是能成功将任務添加到任務隊列中,是以線程數量不再增加。若任務建立的速度遠大于任務處理的速度,無界隊列會快速增長,直到記憶體耗盡。

handler

JDK内置了四種拒絕政策:

  • ​DiscardOldestPolicy政策​

    ​:丢棄任務隊列中最早添加的任務,并嘗試送出目前任務;
  • ​CallerRunsPolicy政策​

    ​:調用主線程執行被拒絕的任務,這提供了一種簡單的回報控制機制,将降低新任務的送出速度。
  • ​DiscardPolicy政策​

    ​:默默丢棄無法處理的任務,不予任何處理。
  • ​AbortPolicy政策​

    ​:直接抛出異常,阻止系統正常工作。

繼續閱讀