前言
最近看阿里的 Java開發手冊,上面有線程池的一個建議:
【強制】線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,
這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。
結合最近面試的經歷,發現這條建議還是十分有用的,因為自己經常使用Executors提供的工廠方法創建線程池,所以忽略了線程池內部的實現。
特別是拒絕策略,面試被問到兩次,因為使用Executors創建線程池不會傳入這個參數而使用默認值所以我們常常忽略這一參數,還好因為這條建議,自己提前熟悉了一下ThreadPoolExecutor。
ThreadPoolExecutor
先看看如何使用ThreadPoolExecutor創建線程池:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize - 線程池核心池的大小。
maximumPoolSize - 線程池的最大線程數。
keepAliveTime - 當線程數大於核心時,此為終止前多余的空閑線程等待新任務的最長時間。
unit - keepAliveTime 的時間單位。
workQueue - 用來儲存等待執行任務的隊列。
threadFactory - 線程工廠。
handler - 拒絕策略。
關注點1 線程池大小
線程池有兩個線程數的設置,一個為核心池線程數,一個為最大線程數。
在創建了線程池后,默認情況下,線程池中並沒有任何線程,等到有任務來才創建線程去執行任務,除非調用了prestartAllCoreThreads()或者prestartCoreThread()方法
當創建的線程數等於 corePoolSize 時,會加入設置的阻塞隊列。當隊列滿時,會創建線程執行任務直到線程池中的數量等於maximumPoolSize。
關注點2 適當的阻塞隊列
java.lang.IllegalStateException: Queue full
方法 拋出異常 返回特殊值 一直阻塞 超時退出
插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
檢查方法 element() peek() 不可用 不可用
ArrayBlockingQueue :一個由數組結構組成的有界阻塞隊列。
LinkedBlockingQueue :一個由鏈表結構組成的有界阻塞隊列。
PriorityBlockingQueue :一個支持優先級排序的無界阻塞隊列。
DelayQueue: 一個使用優先級隊列實現的無界阻塞隊列。
SynchronousQueue: 一個不存儲元素的阻塞隊列。
LinkedTransferQueue: 一個由鏈表結構組成的無界阻塞隊列。
LinkedBlockingDeque: 一個由鏈表結構組成的雙向阻塞隊列。
關注點3 明確拒絕策略
ThreadPoolExecutor.AbortPolicy: 丟棄任務並拋出RejectedExecutionException異常。 (默認)
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然后重新嘗試執行任務(重復此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務
說明:Executors 各個方法的弊端:
1)newFixedThreadPool 和 newSingleThreadExecutor:
主要問題是堆積的請求處理隊列可能會耗費非常大的內存,甚至 OOM。
2)newCachedThreadPool 和 newScheduledThreadPool:
主要問題是線程數最大數是 Integer.MAX_VALUE,可能會創建數量非常多的線程,甚至 OOM。
Executors
讓我們再看看Executors提供的那幾個工廠方法。
newSingleThreadExecutor
創建一個單線程的線程池。這個線程池只有一個線程在工作,也就是相當於單線程串行執行所有任務。如果這個唯一的線程因為異常結束,那么會有一個新的線程來替代它。
此線程池保證所有任務的執行順序按照任務的提交順序執行。
new ThreadPoolExecutor(1, 1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>())
newFixedThreadPool
創建固定大小的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。
線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那么線程池會補充一個新線程。
new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
newCachedThreadPool
創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,
那么就會回收部分空閑(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。
此線程池不會對線程池大小做限制,線程池大小完全依賴於操作系統(或者說JVM)能夠創建的最大線程大小。
new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());