15. 線程池
15.1 爲什麼要使用線程池?
-
降低資源消耗:通過重複利用已創建的線程降低線程創建和銷燬造成的消耗
-
提高響應速度:任務到達時,任務可以不需要等到線程創建就能立即執行
-
提高線程的可管理性
15.2 線程池的體系結構:
java.util.concurrent.Executor : 負責線程的使用與調度的根接口
- ExecutorService 子接口: 線程池的主要接口
- ThreadPoolExecutor 線程池的實現類
- ScheduledExecutorService 子接口:負責線程的調度
- ScheduledThreadPoolExecutor :繼承 ThreadPoolExecutor,實現 ScheduledExecutorService
15.3 創建線程池的方法
private static ExecutorService executor = new ThreadPoolExecutor(10, 10,
60L, TimeUnit.SECONDS, new ArrayBlockingQueue(10));
/***********************************************************************/
public ThreadPoolExecutor(int corePoolSize, //核心池大小大小
int maximumPoolSize, //最大容量
long keepAliveTime, //線程數大於corePoolSize後,空閒存活時間
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;
}
15.3.1 線程池的拒絕策略
等待隊列也已經滿了,再也塞不下新任務了。同時線程池中的max線程數也達到了,無法繼續爲新任務服務。這時候我們就需要拒絕策略機制合理的解決這個問題。
-
AbortPolicy 默認 拋出RejectedExecutionException異常阻止系統正常運行
-
CallerRunsPolicy 該策略既不會拋出任務,也不會拋出異常,而是將某些任務交由調用者完成。
-
DiscardOldestPolicy 拋棄隊列中等待最久的任務,然後把當前任務加入到隊列中嘗試再次提交當前任務。
-
DiscardPolicy 直接丟棄任務,不予任何處理也不拋出異常。如果允許任務丟失,這是最好的一種方案。
15.3.2 線程池的工作隊列
-
ArrayBlockingQueue (有界隊列)
-
LinkedBlockingQueue (無界隊列)可以指定對立的大小,也可以不指定,默認類似一個無限大小的容量(Integer.MAX_VALUE)
-
SynchronousQueue(同步隊列) 不存儲元素,每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態, 吞吐量通常要高於LinkedBlockingQueue
-
DelayQueue(延遲隊列) 一個任務定時週期的延遲執行的隊列。根據指定的執行時間從小到大排序,否則根據插入到隊列的先後排序
-
PriorityBlockingQueue(優先級隊列)
有界隊列即長度有限,滿了以後ArrayBlockingQueue會插入阻塞。無界隊列就是裏面能放無數的東西而不會因爲隊列長度限制被阻塞,但是可能會出現OOM異常。
15.4 線程池的提交與關閉方法
- threadPool.execute(Runnable task) 提交無返回值的方法
- threadPool.submit(Callable task) 提交有返回值的方法,返回一個future對象
- threadPool.shutdown() 等待任務執行完關閉
- threadPool.shutdownNow() 立即關閉
15.5 線程池的底層工作原理
-
在創建線程池後,等待提交過來的任務請求。
-
調用execute()方法提交一個新任務到線程池,處理流程:
- 判斷核心線程池裏的線程是否都在執行任務。如果不是,則創建一個新的工作線程執行任務。
- 判斷工作隊列是否已滿,如果工作隊列沒有滿,則將新提交的任務存儲在這個工作隊列裏。
- 判斷線程池的線程是否都處於工作狀態。如果沒有,則創建新的工作線程來執行任務。如果滿了,則交給飽和策略來處理這個任務。
-
當一個線程完成任務時,它會從隊列中取下一個任務來執行。
-
當一個線程無事可做超過一定的時間(keepAliveTime)時,線程池會判斷:
-
如果當前運行的線程數大於corePoolSize,那麼這個線程就被停掉。
-
所以當線程池的所有任務完成後,它最終會收縮到corePoolSize的大小。
以ThreadPoolExecutor執行execute方法舉例,分爲4種情況:
-
如果當前運行線程數少於corePoolSize,則創建新線程來執行任務
-
如果運行的線程等於或多餘corePoolSize,則將任務加入BlockingQueue
-
如果BlockingQueue已滿,則創建新的線程來處理任務
-
如果創建新線程將使當前運行的線程超出maximumPoolSize,任務將被拒絕,並調用對應的策略
工作線程:線程池創建線程時,會將線程封裝成工作線程Worker,Worker在執行完任務後,還會循環獲取工作隊列裏 的任務來執行。
15.6 使用線程池的風險
- 死鎖:線程池引入了另一種死鎖可能,所有池程都在執行已阻塞的等待隊列中另一任務的執行結果的任務,但這一任務卻因爲沒有未被佔用的線程而不能運行。
- 資源不足
- 併發錯誤
- 線程泄漏:當從池中一個線程執行任務後該線程卻沒有返回池時,會發生這種情況。
- 請求過載
15.7 創建線程池的工具類 : Executors
線程池種類 | 特點 |
---|---|
newFixedThreadPool() | 創建固定大小的線程池,核心線程數和最大線程數大小一樣,keepAliveTime爲0,阻塞隊列是LinkedBlockingQueue,處理CPU密集型的任務。 |
newCachedThreadPool() | 核心線程數爲0,最大線程數爲Integer.MAX_VALUE,keepAliveTime爲60s,阻塞隊列是SynchronousQueue,併發執行大量短期的小任務。 |
newSingleThreadExecutor() | 創建單個線程池。核心線程數和最大線程數大小一樣且都是1,keepAliveTime爲0,阻塞隊列是LinkedBlockingQueue,按添加順序串行執行任務。 |
newScheduledThreadPool() | 創建固定大小的線程,最大線程數爲Integer.MAX_VALU,阻塞隊列是DelayedWorkQueue |
注意:
- FixedThreadPool 和 SingleThreadPool允許的請求隊列(底層實現是LinkedBlockingQueue)長度爲Integer.MAX_VALUE,可能會堆積大量的請求,從而導致OOM
- CachedThreadPool 和 ScheduledThreadPool 允許的創建線程數量爲Integer.MAX_VALUE,可能會創建大量的線程,從而導致OOM。