為什么阿里Java規約禁止使用Java內置Executors創建線程池?


IDEA導入阿里規約插件,當你這樣寫代碼時,插件就會自動監測出來,並給你紅線提醒。

告訴你手動創建線程池,效果會更好。

在探秘原因之前我們要先了解一下線程池 ThreadPoolExecutor 都有哪些參數及其意義。

ThreadPoolExecutor 構造方法:

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

 參數的意義:

1.corePoolSize 指定了線程池里的線程數量,核心線程池大小

2.maximumPoolSize 指定了線程池里的最大線程數量

3.keepAliveTime 當線程池線程數量大於corePoolSize時候,多出來的空閑線程,多長時間會被銷毀。

4.unit 時間單位。TimeUnit

5.workQueue 任務隊列,用於存放提交但是尚未被執行的任務。

  我們可以選擇如下幾種:

  • ArrayBlockingQueue:基於數組結構的有界阻塞隊列,FIFO。
  • LinkedBlockingQueue:基於鏈表結構的有界阻塞隊列,FIFO。
  • SynchronousQueue:不存儲元素的阻塞隊列,每個插入操作都必須等待一個移出操作,反之亦然。
  • PriorityBlockingQueue:具有優先級別的阻塞隊列。

6.threadFactory 線程工廠,用於創建線程,一般可以用默認的

7.handler 拒絕策略,所謂拒絕策略,是指將任務添加到線程池中時,線程池拒絕該任務所采取的相應策略。

  什么時候拒絕?當向線程池中提交任務時,如果此時線程池中的線程已經飽和了,而且阻塞隊列也已經滿了,則線程池會選擇一種拒絕策略來處理該任務,該任務會交給RejectedExecutionHandler 處理。

  線程池提供了四種拒絕策略:

  1. AbortPolicy:直接拋出異常,默認策略;
  2. CallerRunsPolicy:用調用者所在的線程來執行任務;
  3. DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務,並執行當前任務;
  4. DiscardPolicy:直接丟棄任務;

 -------

阿里規約之所以強制要求手動創建線程池,也是和這些參數有關。具體為什么不允許,規約是這么說的:

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

Executor提供的四個靜態方法創建線程池,但是阿里規約卻並不建議使用它。

Executors各個方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:
  主要問題是堆積的請求處理隊列可能會耗費非常大的內存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
  主要問題是線程數最大數是Integer.MAX_VALUE,可能會創建數量非常多的線程,甚至OOM。

看一下這兩種弊端怎么導致的。

第一種,newFixedThreadPool和newSingleThreadExecutor分別獲得 FixedThreadPool 類型的線程池 和  SingleThreadExecutor 類型的線程池。 

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

 

 

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

 

因為,創建了一個無界隊列LinkedBlockingQueuesize,是一個最大值為Integer.MAX_VALUE的線程阻塞隊列,當添加任務的速度大於線程池處理任務的速度,可能會在隊列堆積大量的請求,消耗很大的內存,甚至導致OOM。

 

第二種,newCachedThreadPool 和 newScheduledThreadPool創建的分別是CachedThreadPool 類型和 ScheduledThreadPoolExecutorScheduledThreadPoolExecutor類型的線程池。

CachedThreadPool是一個會根據需要創建新線程的線程池 ,ScheduledThreadPoolExecutor可以用來在給定延時后執行異步任務或者周期性執行任務。

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

 

 

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
}

 

創建的線程池允許的最大線程數是Integer.MAX_VALUE,空閑線程存活時間為0,當添加任務的速度大於線程池處理任務的速度,可能會創建大量的線程,消耗資源,甚至導致OOM。

這兩種都是有點極端的,稍微點進去看一下源碼就能看出來。

 

阿里規約提倡手動創建線程池,而非Java內置的線程池,給出的正例如下:

正例1:

//org.apache.commons.lang3.concurrent.BasicThreadFactory
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
    new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());

 

正例2:

ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();

//Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

pool.execute(()-> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown

 

正例3:

<bean id="userThreadPool"
    class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
  <property name="corePoolSize" value="10" />
  <property name="maxPoolSize" value="100" />
  <property name="queueCapacity" value="2000" />

  <property name="threadFactory" value= threadFactory />
  <property name="rejectedExecutionHandler">
    <ref local="rejectedExecutionHandler" />
  </property>
</bean>
//in code
userThreadPool.execute(thread);

 

IDEA安裝阿里規約插件,就能提示你不要這么使用。

 

鼠標放上去,就會給你提示信息。

---

這個插件還會有很多提示,相信很多Java程序員都拜讀過阿里的Java規約,該規約被很多公司拿來作為自己的開發規范。
規約里的開發規范這個插件都能提示,當你不符合規約時。你可以選擇關閉實時監測,也可以選擇在某一時刻全部掃描。在IDEA的插件安裝面板里搜Alibaba Java Coding Guidelines plugin support,安裝即可。
 
另外如需獲取阿里最新Java開發規約(已升級為《Java開發手冊-華山新版》)請關注公眾號編程大道,回復“手冊”獲取。

 

 



免責聲明!

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



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