阿里代碼規範中規定,線程資源必須通過線程池提供,不允許在應用中自行顯示的創建線程。這樣做的好處是減少在創建和銷燬所花的時間和系統開銷。不使用線程池可能造成創建大量同類線程而導致消耗內存或則“過度切換”的問題。並且規定線程池不允許使用Executors創建。那麼創建線程的方式基本就依賴於ThreadPoolExecutor此類了。
先了解下ThreadPoolExecutor的構造函數:
//構造函數使用默認的 DefaultThreadFactory 以及默認的AbortPolicy
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler);
}
//使用默認的拒絕策略
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler);
}
//以上兩個兩個構造函數調用此函數
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler);
}
//以上三個函數調用此函數
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
以上Overloading最終調用最後一個函數,集中解析下最後一個構造函數的參數就可以了。
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler)
corepoolsize:核心線程數 線程池創建後激活的線程數量
maxinumpoolSize:最大線程數 線程池提交的線程數,如果隊列已經滿了,而且超出最大線程數,會使用拒絕策略
keepAliveTime和timeUnit配置使用表示超過核心線程數的線程最大的空閒存貨時間和單位
blockingQueue:保存等待執行的任務的阻塞隊列 參數可以如下
ArrayBlockingQueue:是一個基於數組結構的有界阻塞隊列,按FIFO原則進行排序
LinkedBlockingQueue:一個基於鏈表結構的阻塞隊列,吞吐量高於ArrayBlockingQueue。
SynchronousQueue: 一個不存儲元素的阻塞隊列。每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態,吞吐量高於LinkedBlockingQueue。
PriorityBlockingQueue:一個具有優先級的無限阻塞隊列。
threadFactory:通過線程工廠爲每個創建出來的線程設置更有意義的名字
RejectedExecutionHandler:當隊列和線程池都滿了,說明線程池處於飽和狀態,那麼必須採取一種策略還處理新提交的任務。它可以有如下四個選項:
AbortPolicy:直接拋出異常,默認情況下采用這種策略
CallerRunsPolicy:只用調用者所在線程來運行任務
DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務
DiscardPolicy:不處理,丟棄掉
有了以上定義好的數據,下面來看看內部是如何實現的 。整個思路總結起來就是 5 句話:
- 如果當前池大小 poolSize 小於 corePoolSize ,則創建新線程執行任務。
- 如果當前池大小 poolSize 大於 corePoolSize ,且等待隊列未滿,則進入等待隊列
- 如果當前池大小 poolSize 大於 corePoolSize 且小於 maximumPoolSize ,且等待隊列已滿,則創建新線程執行任務。
- 如果當前池大小 poolSize 大於 corePoolSize 且大於 maximumPoolSize ,且等待隊列已滿,則調用拒絕策略來處理該任務。
- 線程池裏的每個線程執行完任務後不會立刻退出,而是會去檢查下等待隊列裏是否還有線程任務需要執行,如果在 keepAliveTime 裏等不到新的任務了,那麼線程就會退出。
我們看下常用Executors的實現:
//固定大小的線程池,多餘的線程放入linkedBlockingQueue中,然後從對列中取線程
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
//synchronousQueue不存儲元素,有元素立即分配線程
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//其實就是單位爲1的fixedThreaPool
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
看了這麼多實例我們可知,以上線程池如果線程過多可能會造成大量堆積待處理線程或則創建大量線程的問題。靈活使用ThreadPoolExecutor可以更加明確線程運行規則,避免資源耗盡的風險。
歡迎留言交流,共同討論