為什麼阿里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 處理。
執行緒池提供了四種拒絕策略:
- AbortPolicy:直接拋出異常,默認策略;
- CallerRunsPolicy:用調用者所在的執行緒來執行任務;
- DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務,並執行當前任務;
- 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安裝阿里規約插件,就能提示你不要這麼使用。
滑鼠放上去,就會給你提示資訊。
—