Java中的線程池是運用場景最多的并發架構,幾乎所有需要異步或并發執行任務的程式都可以使用線程池,是以我們就要認識并弄懂線程池,以便于更好的為我們業務場景服務。
一、線程池的好處
在開發過程中,合理地使用線程池大緻有3個好處
第一:降低資源消耗。通過重複利用已建立的線程降低線程建立和銷毀造成的消耗。
第二:提高響應速度。當任務到達時,任務可以不需要等到線程建立就能立即執行。
第三:提高線程的可管理性。線程是稀缺資源,如果無限制地建立,不僅會消耗系統資源,
還會降低系統的穩定性,使用線程池可以進行統一配置設定、調優和監控。但是,要做到合理利用
線程池,必須對其實作原理了如指掌
二、線程池工作流程
1)當送出一個新任務到線程池時,線程池判斷corePoolSize線程池是否都在執行任務,如果有空閑線程,則從核心線程池中取一個線程來執行任務,直到目前線程數等于corePoolSize;
2)如果目前線程數為corePoolSize,繼續送出的任務被儲存到阻塞隊列中,等待被執行;
3)如果阻塞隊列滿了,那就建立新的線程執行目前任務,直到線程池中的線程數達到maxPoolSize,這時再有任務來,由飽和政策來處理送出的任務
三、線程池參數
下面是ThreadPoolExecutor類的構造方法傳參數
pblic ThreadPoolExecutor(int corePoolSize, #核心線程數
int maximumPoolSize, #最大線程數
long keepAliveTime, #達到最大線程數數時候,線程池的工作線程空閑後,保持存活的時間
TimeUnit unit, #keepAliveTime機關
BlockingQueue<Runnable> workQueue #阻塞隊列
RejectedExecutionHandler handler #飽和政策
) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
new ThreadPoolExecutor(6 ,12, 5L, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(10),new ThreadPoolExecutor.CallerRunsPolicy());
比如corePoolSize為6, maximumPoolSize為,12 keepAliveTime為5秒,隊列長度為10;送出任務數達到核心線程數6時候,新來的任務就會被放入LinkedBlockingQueue阻塞隊列,當隊列任務數達到10個時候,就會建立新線程執行任務,直到達到maximumPoolSize數量12;如果還有新來的任務,由政策來處理送出的任務;如果沒有,線程池空閑時候,超過5秒,建立的maximumPoolSize,就會被銷毀。
四、阻塞隊列
阻塞隊列BlockingQueue接口,從jdk1.5開始,有四個實作類,jdk8亦是如此
ArrayBlockingQueue:是一個基于數組結構的有界阻塞隊列,此隊列按FIFO(先進先出)原則對元素進行排序。
LinkedBlockingQueue:一個基于連結清單結構的阻塞隊列,此隊列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue,靜态工廠方法Executors.newFixedThreadPool()使用了這個隊列。
SynchronousQueue:一個不存儲元素的阻塞隊列。每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處于阻塞狀态,吞吐量通常要高于LinkedBlockingQueue,靜态工廠方法Executors.newCachedThreadPool使用了這個列。
PriorityBlockingQueue:一個具有優先級的無限阻塞隊列。
五、四種飽和政策
RejectedExecutionHandler(飽和政策):當隊列和線程池都滿了,說明線程池處于飽和狀态,那麼必須采取一種政策處理送出的新任務。這個政策預設情況下是AbortPolicy,表示無法處理新任務時抛出異常,檢視源碼,從jdk5開始RejectedExecutionHandler接口有四個實作類,jdk8亦是如此。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuIzNzIDOyMjM5ATMxgTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
AbortPolicy:不處理,直接抛出異常。
CallerRunsPolicy:若線程池還沒關閉,調用目前所線上程來運作任務,r.run()執行。
DiscardOldestPolicy:LRU政策,丢棄隊列裡最近最久不使用的一個任務,并執行目前任務。
DiscardPolicy:不處理,丢棄掉,不抛出異常。
六、向線程池送出任務
可以使用兩個方法向線程池送出任務,分别為execute()和submit()方法
1、execute()方法用于送出不需要傳回值的任務,是以無法判斷任務是否被線程池執行成功
2、submit()方法用于送出需要傳回值的任務。
線程池會傳回一個future類型的對象,通過這個future對象可以判斷任務是否執行成功,并且可以通過future的get()方法來擷取傳回值,get()方法會阻塞目前線程直到任務完成,而使用get(long timeout,TimeUnit unit),在指定的時間内會等待任務執行,逾時則抛出逾時異常,等待時候會阻塞目前線程
七、關閉線程池
ThreadPoolExecutor提供了兩個方法,用于線程池的關閉,分别是shutdown()和shutdownNow(),它們的原理是周遊線
程池中的工作線程,然後逐個調用線程的interrupt方法來中斷線程
shutdown():不會立即終止線程池,而是要等所有任務緩存隊列中的任務都執行完後才終止,但再也不會接受新的任務
shutdownNow():立即終止線程池,并嘗試打斷正在執行的任務,并且清空任務緩存隊列,傳回尚未執行的任務
隻要調用了這兩個關閉方法中的任意一個,isShutdown方法就會傳回true。當所有的任務
都已關閉後,才表示線程池關閉成功,這時調用isTerminaed方法會傳回true
是以,判斷線程池所有線程是否執行完成,可以這樣寫
while(true){//死循環
if(threadPool.isTerminated()) {
//執行自己的操作
break;//true停止
}
Thread.sleep(500);//休眠500繼續循環
}
shutdown,隻是将線程池的狀态設定成SHUTDOWN狀态,然後中斷所有沒有正在執行任務的線
程,等待執行任務的線程完成。
shutdownNow首先将線程池的狀态設定成STOP,然後嘗試停止所有的正在執行或暫停任務的線程,
并傳回等待執行任務的清單
八、線程池狀态
線程池有五種運作狀态
1、RUNNING
(1) 狀态說明:線程池處在RUNNING狀态時,能夠接收新任務,以及對已添加的任務進行處理。
(2) 狀态切換:線程池的初始化狀态是RUNNING。線程池被一旦被建立,就處于RUNNING狀态,且線程池中的任務數為0
2、 SHUTDOWN
(1) 狀态說明:線程池處在SHUTDOWN狀态時,不接收新任務,但能處理已添加的任務。
(2) 狀态切換:調用線程池的shutdown()接口時,線程池由RUNNING -> SHUTDOWN。
3、STOP
(1) 狀态說明:線程池處在STOP狀态時,不接收新任務,不處理已添加的任務,并且會中斷正在處理的任務。
(2) 狀态切換:調用線程池的shutdownNow()接口時,線程池由(RUNNING or SHUTDOWN ) -> STOP。
4、TIDYING
(1) 狀态說明:當所有的任務已終止,ctl記錄的”任務數量”為0,線程池會變為TIDYING狀态。當線程池變為TIDYING狀态時,會執行鈎子函數terminated()。terminated()在ThreadPoolExecutor類中是空的,若使用者想線上程池變為TIDYING時,進行相應的處理;可以通過重載terminated()函數來實作。
(2) 狀态切換:當線程池在SHUTDOWN狀态下,阻塞隊列為空并且線程池中執行的任務也為空時,就會由 SHUTDOWN -> TIDYING。 當線程池在STOP狀态下,線程池中執行的任務為空時,就會由STOP -> TIDYING。
5、 TERMINATED
(1) 狀态說明:線程池徹底終止,就變成TERMINATED狀态。
(2) 狀态切換:線程池處在TIDYING狀态時,執行完terminated()之後,就會由 TIDYING -> TERMINATED。
源碼中可以看到
private static final int COUNT_BITS = Integer.SIZE - 3;//32-3=29
// runState is stored in the high-order bits 運作狀态存儲在高位
//左移動29位,即-1的29次方,轉換為二進制11100000000000000000000000000000
private static final int RUNNING = -1 << COUNT_BITS;
//即0的29次方,轉換為二進制00000000000000000000000000000000
private static final int SHUTDOWN = 0 << COUNT_BITS;
//即1的29次方,轉換為二進制00100000000000000000000000000000
private static final int STOP = 1 << COUNT_BITS;
//即2的29次方,轉換為二進制01000000000000000000000000000000
private static final int TIDYING = 2 << COUNT_BITS;
//即3的29次方,轉換為二進制01100000000000000000000000000000
private static final int TERMINATED = 3 << COUNT_BITS;
合并線程池狀态與工作線程數量:ctlOf
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//表示線程池最大容量,1的29次方減去1,即536870911,二進制表示00011111111111111111111111111111
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
//rs線程池狀态,wc工作線程數量,兩者按位或,得到ctl ,高3位表示線程池狀态,低29位表示工作線程數量
private static int ctlOf(int rs, int wc) { return rs | wc; }
線程池狀态:runStateOf
private static int runStateOf(int c) { return c & ~CAPACITY; }
描述:參數c=ctl.get(),get到合并值ctl,c & ~CAPACITY與運算,得到高3位就是線程狀态
釋義:CAPACITY = (1 << COUNT_BITS) - 1的值536870911,"~"表示取反
536870911轉換二進制 00011111111111111111111111111111,取反後 11100000000000000000000000000000
參數c轉為二進制後,其高3位與111按位與,得到的高3位是參數c的高3位
CAPACITY取反的低29位為零,是以參數c與之低29位按位與,得到的低29位是CAPACITY取反的低29位,全為0
示例:
xxx0000000000000000000000001100 & 11100000000000000000000000000000
按位與後xxx000000000000000000000000000 即取參數c的高3位,CAPACITY的低29位全為0
所為參數c,即ctl,高3位表示線程池狀态
工作線程數量:workerCountOf
private static int workerCountOf(int c) { return c & CAPACITY; }
描述:參數c=ctl.get(),get到合并值ctl ,c & CAPACITY運算,得到低29位,即線程池線程數量
釋義:CAPACITY = (1 << COUNT_BITS) - 1的值536870911
536870911轉換二進制 00011111111111111111111111111111
參數c轉為二進制後,其高3位與000按位與後,得到的高3位為CAPACITY 的高3位,即000
CAPACIT的低29位都為1,是以參數c低29位與之按位與,得到的低29位總是參數c的低29位
示例:
010xxxxxxxxxxxxxxxxxxxxxxxxxxxx & 00011111111111111111111111111111
按位與後000xxxxxxxxxxxxxxxxxxxxxxxxxxxx 即取CAPACITY得高3位000,參數c的低29位
所為參數c,即ctl,低29位表示線程數量
九、調整線程池大小
ThreadPoolExecutor提供了setter方法,是以建立線程池之後,也可以動态調整線程池大小
十、監控線程池
可以通過線程池提供的參數進行監控,在監控線程池的時候可以使用以下屬性,擷取線程池任務狀況
taskCount:線程池需要執行的任務數量。
completedTaskCount:線程池在運作過程中已完成的任務數量,小于或等于taskCount。
largestPoolSize:線程池裡曾經建立過的最大線程數量。通過這個資料可以知道線程池是
否曾經滿過。如該數值等于線程池的最大大小,則表示線程池曾經滿過。
getPoolSize:線程池的線程數量。如果線程池不銷毀的話,線程池裡的線程不會自動銷
毀,是以這個大小隻增不減。
getActiveCount:擷取活動的線程數。
也可以通過擴充線程池進行監控。可以通過繼承線程池來自定義線程池,重寫線程池的beforeExecute、afterExecute和terminated方法,也可以在任務執行前、執行後和線程池關閉前執行一些代碼來進行監控。
例如,監控任務的平均執行時間、最大執行時間和最小執行時間等,這幾個方法線上程池裡是空方法
參考:《Java并發程式設計的藝術》
參考:http://blog.51cto.com/10594544/2176728