為什麼阿里Java規約禁止使用Java內置Executors創建執行緒池?

  • 2019 年 10 月 3 日
  • 筆記

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開發手冊-華山新版》)請關注公眾號編程大道,回復“手冊”獲取。