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安裝阿里規約插件,就能提示你不要這么使用。
鼠標放上去,就會給你提示信息。
---