掃描下方二維碼或者微信搜索公衆號
菜鳥飛呀飛
,便可關注微信公衆號,閱讀更多Spring源碼分析
和Java併發編程
文章。java
在《阿里巴巴Java開發手冊》第一章第6講併發處理
中,強制規定了線程池不容許使用Executors去建立。那麼爲何呢?這就得從線程池和Executors這個類的本質上提及了。面試
在Java中提供了兩種類型的線程池來供開發人員使用,分別是ThreadPoolExecutor
和ScheduledThreadPoolExecutor
。其中ScheduledThreadPoolExecutor繼承了ThreadPoolExecutor,類的UML圖以下所示。ScheduledThreadPoolExecutor的功能和Java中的Timer相似,它提供了定時的去執行任務或者固定時延的去執行任務的功能,其功能比Timer更增強大。(關於線程池的原理及詳細的源碼分析,能夠參考這篇文章:線程池ThreadPoolExecutor的實現原理)編程
參數 | 功能 |
---|---|
int corePoolSize | 線程池的核心線程數 |
int maximumPoolSize | 線程池的最大線程數 |
long keepAliveTime | 空閒線程的最大空閒時間 |
TimeUnit unit | 空閒時間的單位,TimeUnit 是一個枚舉值,它能夠是納秒、微妙、毫秒、秒、分、小時、天 |
BlockingQueue workQueue | 存聽任務的阻塞隊列,經常使用的阻塞隊列有ArrayBolckingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityQueue |
ThreadFactory threadFactory | 建立線程的工廠,一般利用線程工廠建立線程時,賦予線程具備業務含義的名稱 |
RejectedExecutionHandler handler | 拒絕策略。當線程池線程數超過最大線程數時,線程池沒法再接收任務了,這個時候須要執行拒絕策略 |
爲何說這7個參數十分重要呢?由於線程池ThreadPoolExecutor的實現原理就是依靠這幾個參數來實現的。當主線程提交一個任務到線程池後,線程池的執行流程以下:設計模式
1.
先判斷線程池中線程的數量是否小於核心線程數,即:是否小於corePoolSize
,若是小於corePoolSize,就建立新的線程去執行任務;不然就進入到下面流程。2.
判斷任務隊列是否已經滿了,即:判斷workQueue
有沒有滿,若是沒有滿,就將任務添加到任務隊列中;若是已經滿了,就進入到下面的流程。3.
再判斷若是新建立一個線程後,線程數是否會大於最大線程數,即:是否大於maximumPoolSize
,若是大於maximumPoolSize,則進入到下面的流程;不然就建立一個新的線程來執行任務。4.
執行拒絕策略,即執行handler
的rejectedExecution()
方法 因爲ThreadPoolExecutor
類的構造方法的參數太多了,建立起來比較麻煩,並且ThreadPoolExecutor又能夠細分爲三種類型的線程池,這樣建立起來不太方便。這個時候,工廠設計模式就派上用場了,Executors就是這樣的一個靜態工廠類,它裏面提供了靜態方法,調用這些靜態方法,傳入較少的參數或者不傳參數,咱們就能夠很輕鬆地建立出線程池。Executors其實就是一個工具類,專門用來建立線程池。 上面提到ThreadPoolExecutor有7個很是重要的參數,咱們在給這些參數傳入特殊的值的時候,建立出來的ThreadPoolExecutor線程池又能夠細分爲三類:FixedThreadPool
(線程數量固定的線程池)、SingleThreadExecutor
(單線程的線程池)、CachedThreadPool
(線程數大小無界的線程池)。(注意:這裏的FixedThreadPool、SingleThreadExecutor、CachedThreadPool不是實際的類名,而是根據線程池的特殊性來取的別名
)。下面來具體看下這三種線程池。服務器
FixedThreadPool是線程數量固定的線程池
,即核心線程數與最大線程數相等
。Executors工廠類提供了以下兩個方法去建立FixedThreadPool。微信
// 指定線程數
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
// 指定線程數和線程工廠
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
複製代碼
能夠發現,在Executors工廠類中,是直接調用了ThreadPoolExecutor的構造方法,並且令核心線程數和最大線程數均等於傳入的參數nThreads,當線程數量達到核心線程數後,線程數就不會在變化了,始終維持在覈心線程數這個數值,所以這種方法建立出來的線程池稱之爲線程數量固定的線程池。 同時咱們還發現,參數keepAliveTime參數的值被設置爲0,這是由於當coolPoolSize等於maximumPoolSize時,線程池中始終是不會存在空閒線程的,而keepAliveTime參數的含義是空閒線程存活的最大時間,都不可能出現空閒線程了,設置keepAliveTime的值大於0也就沒有任何意義了,所以這裏將其設置爲0。 此時任務隊列使用的是LinkedBlockingQueue
,因爲LinkedBlockingQueue在初始化時,若是不顯示指定大小,就會默認隊列的大小爲Integer.MAX_VALUE
,這個數值很是大了,所以一般稱它是一個無界隊列。 當使用無界隊列時,會形成如下問題:多線程
1.
當線程數達到核心線程數後,新添加的任務會被放入到任務隊列中,因爲使用無界隊列,那麼就能夠無限制的向隊列中添加任務,這有可能形成OOM。同時因爲任務隊列中能一直存聽任務,那麼就會致使maximunPoolSize這個參數失效。2.
使用無界隊列,致使線程數不會超過maximunPoolSize,就不會出現空閒線程,也就是將致使keepAliveTime這個參數失效。3.
使用無界隊列,致使線程數不會超過maximunPoolSize,那麼就永遠不會執行拒絕策略,也就是handler參數失效。 對於服務器負載較高的應用,因爲須要嚴格管控資源,所以在應用中不能隨意建立線程,這個時候適合使用FixedThreadPool,由於此時線程數固定,只要提早預判好線程數,就不會形成因線程池配置不當而致使服務異常的現象。 SingleThreadExecutor
,線程數爲1的線程池,即核心線程數與最大線程數均爲1。Executors工廠類提供了以下兩個方法去建立SingleThreadExecutor。併發
// 不須要傳遞任何參數,在ThreadPoolExecutor的構造方法中,直接令核心線程數和最大線程數爲1
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
// 指定一個線程建立工廠便可,而後在ThreadPoolExecutor的構造方法中,令核心線程數和最大線程數爲1
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
複製代碼
從上面代碼中,能夠發現,在ThreadPoolExecutor的構造方法中,直接令maximunPoolSize和corePoolSize的值均爲1,這樣線程池中就會一直只存在一個線程,即單線程的線程池。一樣,由於不會出現存在空閒線程的狀況,所以將keepAliveTime設置爲0。任務隊列依然使用的是LinekdBlockingQueue,即無界隊列。因爲使用無界隊列,所以仍然可能會形成OOM異常,以及keepAliveTime、maximunPoolSize、handler等參數失效。 對於須要保證任務順序執行的場景,可使用SingleThreadExecutor。工具
CachedThreadPool
,線程數大小無界的線程池。核心線程數等於0,最大線程數等於Integer.MAX_VALUE
,這個值已經很是大了,所以稱之爲線程數大小無界的線程池。Executors工廠類提供了以下兩個方法去建立CachedThreadPool。源碼分析
// 不要傳任何參數,在ThreadPoolExecutor的構造方法中,令核心線程數爲0,最大線程數爲Integer.MAX_VALUE
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
// 指定線程建立工廠,而後在ThreadPoolExecutor的構造方法中,令核心線程數爲0,最大線程數爲Integer.MAX_VALUE
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
複製代碼
從上面的代碼中能夠發現,在ThreadPoolExecutor的構造方法中,令核心線程數爲0,最大線程數爲Integer.MAX_VALUE
,因爲Integer.MAX_VALUE的值很是大,所以一般也稱CachedThreadPool爲線程數大小無界的線程池。令keepAliveTime等於60,單位爲秒,這說明空閒線程最多存活60秒。 在CachedPoolPool中,使用的阻塞隊列再也不是LinkedBlockingQueue,而是SynchronousQueue
,這是一個不存儲元素的阻塞隊列
。它的特色是,當前線程向隊列中put元素時,必需要等另一個線程從隊列中取出元素後,當前線程纔會返回;若是沒有線程從隊列中取出元素,那麼當前線程就會一直阻塞,直到元素被取出。所以稱SynchronousQueue是一個不存儲元素的隊列。(注意:這裏說的是put操做會阻塞,而offer操做是不阻塞的) 因爲核心線程數爲0,因此當有任務提交到線程池時,第一層判斷不成立(即當前線程數小於核心線程數判斷不成立,此時均爲0)。所以會調用阻塞隊列的offer()
方法嘗試將任務添加到任務隊列中,因爲此時的阻塞隊列是SynchronousQueue,它不存儲元素,所以offer()方法會返回false,這樣就表示第二層判斷不成立(任務沒法添加到隊列)。就接着判斷當前線程數是否大於最大線程數,顯然此時沒有,由於最大線程數爲Integer.MAX_VALUE,因此此時會建立新的線程去處理任務。這樣只要當有新的任務進入到池中時,就會建立新的線程去處理任務,所以稱CachedThreadPool是一個線程數無界的線程池。池中的線程最多空閒60秒,當60秒內沒有從阻塞隊列中獲取到任務後,線程就會被銷燬
。當主線程提交任務的速度大於線程池處理任務的速度時,線程池就會一直建立線程,所以最終有可能形成OOM異常。 當任務較多,但任務執行時間較短時,適合使用CacheThreadPool這種線程池來處理任務。
JUC包下還提供了一種很經常使用的線程池,它就是ScheduledThreadPoolExecutor
。ScheduledThreadPoolExecutor是ThreadPoolExecutor的子類,它的功能是按期執行任務或者在給定的延時以後執行任務。將線程池的核心參數設置爲特殊的值,就會建立出兩種類型的ScheduledThreadPoolExecutor。分別是包含多個線程的ScheduledThreadPoolExecutor和只包含一個線程的SingleScheduledThreadExecutor。(注意:SingleScheduledThreadExecutor不是一個類名,而是根據線程池的特性來取的一個名稱)。 一樣,Executors靜態工廠類也爲ScheduledThreadPoolExecutor的建立提供了相關的靜態方法。下面結合代碼示例來分別分析兩種類型的ScheduledThreadPoolExecutor。
當ScheduledThreadPoolExecutor的核心線程數指定爲多個時(大於1),ScheduledThreadPoolExecutor就是多線程的線程池。Executors工廠類提供了以下兩個方法去建立多個線程的ScheduledThreadPoolExecutor。
// 指定核心線程數的數量
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
// 指定核心線程數的數量和線程工廠
public static ScheduledExecutorService newScheduledThreadPool( int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
複製代碼
當傳入的參數corePoolSize大於1時,就是多線程的ScheduledThreadPoolExecutor,當傳入的數值等於1時,就變成了單線程的SingleThreadScheduledExecutor。下面來看下ScheduledThreadPoolExecutor帶有一個參數的構造方法。源碼以下:
public ScheduledThreadPoolExecutor(int corePoolSize) {
// 核心線程數爲傳入的線程數,即1
// 最大線程數爲Integer.MAX_VALUE
// 使用的阻塞隊列是DelayedWorkQueue,這是一個無界隊列
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
複製代碼
能夠發現,ScheduledThreadPoolExecutor的最大線程數爲Integer.MAX_VALUE
,使用的是DelayedWorkQueue
隊列,這是一個無界隊列。因爲是無界隊列,那麼就會是最大線程數maximumPoolSize這個參數無效,因此即便將最大線程數爲Integer.MAX_VALUE也沒有什麼用處。 DelayedWorkQueue又是一個什麼隊列呢?它是ScheduledThreadPoolExecutor定義的一個靜態內部類,它的本質就是一個延時隊列,其功能和DelayQueue相似。在DelayQueue中,包含了一個PriorityQueue(具備優先級的隊列)類型的屬性,而DelayedWorkQueue是DelayQueue和PriorityQueue的結合體
,它會將提交到線程池的任務封裝成一個RunnableScheduledFuture
對象,而後將這些對象按照必定規則排好序
。 RunnableScheduledFuture
是ScheduledThreadPoolExecutor的一個私有內部類,繼承了FutureTask
。它包含三個很是重要的屬性:
1.
sequenceNumber,任務被添加到線程池時的序號2.
time,任務在哪一個時間點執行3.
period,任務執行的週期 DelayedWorkQueue會將隊列中全部的RunnableScheduledFuture按照每一個RunnableScheduledFuture的time按照從小到大排序
,時間最小的應該最早被執行,因此排在最前面,當出現多個任務的時間相同時,就按照sequenceNumber
這個序號從小到大排序,這樣線程池中就能定時的執行這些任務了。
ScheduledThreadPoolExecutor執行任務的詳細步驟以下:
1.
從DelayedWorkQueue隊列中經過peek()獲取第一個任務,判斷任務的執行時間是否小於當前時間,若是不小於,則說明還沒到任務的執行時間,就讓線程再繼續等待一段時間;若是小於或者等於,就執行下面的流程。2.
經過poll()操做從隊列中取出第一個任務,若是隊列中還有任務,就喚醒處於等待隊列中的線程,通知它們也來嘗試獲取任務。3.
當前線程執行取出的任務。4.
執行完任務後,修改RunnableScheduledFuture任務的time屬性的值,將其設置爲下次將要在被執行的時間點,而後將任務放回到任務隊列中。 SingleThreadScheduledExecutor
指的是線程池只有一個線程的ScheduledThreadPoolExecutor,此時核心線程數爲1。 Executors工廠類提供了以下兩個方法去建立SingleScheduledThreadExecutor。
// 不須要傳任何參數,直接指定核心線程數爲1
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
// 將ScheduledThreadPoolExecutor包裝成了DelegatedScheduledExecutorService
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
// 傳入線程工廠,而後指定核心線程數爲1
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1, threadFactory));
}
複製代碼
SingleScheduledThreadExecutor執行任務的邏輯和多線程的ScheduledThreadPoolExecutor同樣。惟一的區別就是它只有一個線程來執行任務,所以它能保證任務的執行順序,適用於須要保證任務按照順序執行的場景。