主要處理流程:java
(1)判斷核心線程池是否已滿,若是還沒滿則建立線程執行任務,不然進入下一步數據庫
(2)判斷工做隊列是否已滿,若是沒滿則將該任務放入工做隊列等待線程來執行,不然進入下一步編程
(3)判斷線程池中的線程是否都處於工做狀態,若是不是則新建立一個線程來執行任務,沒有處於工做狀態的線程被淘汰,不然按照飽和策略處理該任務。併發
1:當一個任務提交時,若是CorePool
中的核心線程少於CorePoolSize
,則建立一個新線程執行任務(須要全局鎖)源碼分析
2:若是CorePool中沒有空閒的線程,那麼加入BlockingQueue等待覈心線程拉取任務執行性能
3:若是BlockingQueue已滿,建立新線程後若是大於maximumPoolSize
就跳轉到4拒絕執行任務,若是小於就建立新線程執行任務(須要全局鎖)ui
4:四種不一樣的拒絕策略,經過rejectedExecution()
方法執行。spa
須要獲取全局鎖致使線程池的性能大大降低,應該儘可能避免產生步驟1和步驟3線程
public void execute(Runnable command) {
// 若是任務爲空則拋出異常
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 1.工做線程數小於核心線程池大小則添加工做線程
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
// 更新線程池工做狀態
c = ctl.get();
}
// 2.判斷線程池是否處於工做狀態,若是是則嘗試把任務放入阻塞隊列中
if (isRunning(c) && workQueue.offer(command)) {
// 3.再次檢查是否應該回滾任務添加線程,防止任務放入阻塞隊列後線程池down或者工做線程死亡
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
//double check時線程池down了,該任務沒法執行
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 4.沒法加入阻塞隊列或者線程池已down,拒絕任務執行
else if (!addWorker(command, false))
reject(command);
}
複製代碼
1: execute()方法建立一個Worker
線程執行當前任務rest
2:若是Worker
線程數目等於CorePoolSize
,則加入到阻塞隊列中,等待Worker
取出來執行任務
3:若是BlockingQueue
已滿,若是Worker
線程數小於maximumPoolSize
則建立新線程執行任務
4:反覆執行(1)(2)(3)
重點是設置參數,參數設得好,線上沒煩惱
圍繞下面幾個問題思考如何設置線程池
線程池的大小?例如在
2G
內存裏配置一個線程池,如何設置線程池大小執行的任務屬於哪種類型?例如IO密集、CPU密集······
任務是否具備優先級?例如任務的緊急程度、執行時間長短
任務是否具備依賴性?例如依賴數據庫鏈接
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds, runnableTaskQueue, handler);
複製代碼
每次提交任務時,若是當前線程數小於corePoolSize
就會建立一個新線程,即便線程池中有空閒線程,直到線程數等於corePoolSize
。
能夠提早建立好全部線程並啓動,調用prestartAllCoreThreads()
。
保存等待執行的任務隊列,可使用任意阻塞隊列,例如ArrayBlockingQueue
, LinkedBlockingQueue
, PriorityBlockingQueue
, SynchronousQueue
線程池容許的最大線程數,阻塞隊列中的任務已滿(隊列必須有界)時,下一個任務沒法進入阻塞隊列,若是線程池中的線程數小於最大線程數,則嘗試在線程池中繼續建立線程執行任務
給每一個線程設置更有意義的名字的一個工廠,例如ThreadFactoryBuilder
工廠
任務拒絕執行時採用的拒絕策略,有四種:
AbortPolicy
:拋出異常CallerRunsPolicy
:使用調用者線程執行任務DiscardOldestPolicy
:丟棄隊列中最近的一個任務,並執行當前任務DiscardPolicy
:直接丟棄線程池的工做線程空閒後能夠保持存活的時長。若是任務不少且每一個任務的執行時間很短,能夠調大存活時長提升線程利用率。
DAYS
/HOURS
/MINUTES
/MILLISECONDS
(毫秒)/MICROSECONDS
(微妙)/NANOSECONDS
(納秒)
兩個核心方法:有返回值的submit()
和無返回的execute()
線程池會返回一個Future
對象,對象中存儲着任務是否執行成功、返回結果等信息,調用get()
方法能夠獲取返回值,在任務未完成前會一直阻塞,能夠設置超時時長get(long, TimeUnit)
防止一直阻塞。
Future<Object> future = executor.submit(hasReturnValueTask);
try {
future.get();
} catch(Exception e) {
} finally {
// 關閉線程池
executor.shutdown();
}
複製代碼
兩個核心方法:shutdown()
和shutdownNow()
區別:
shutdownNow()
會將線程池狀態設置爲STOP
,而後嘗試終止全部正在執行任務的線程,並返回等待任務的任務列表
shutdown()
會將線程池狀態設置爲SHUTDOWN
,而後中斷全部沒有正在執行任務的線程。
如何選擇:
若是任務不必定執行完,可使用shutdownNow()
,不然使用shutdown()
調用關閉方法後,調用isShutdown()
方法就會返回True
,可是確認線程池的關閉還須要調用isTerminated
方法,這個方法表示全部的任務都已經關閉了。
if(isShutdown() && isTerminated()) {
System.out.print("線程池已關閉!");
}
複製代碼
分析任務特性:
任務的性質:CPU密集、IO密集、混合型(配置線程數)
任務的優先級:高、中和低(配置阻塞隊列)
任務的執行時間:長、中和短(配置線程空閒存活時長)
任務的依賴性:是否依賴其餘系統資源,如數據庫鏈接(配置線程數目)
重點說明任務的依賴性如何肯定線程數目,若是線程須要依賴數據庫鏈接,提交SQL後須要等待數據庫返回結果,這個過程CPU是空閒的,CPU空閒時間越長,那麼線程數應該設置得越大,能夠更好地利用CPU。
最好配置阻塞隊列會有界阻塞隊列,能夠監控線程池的工做狀態,若是大量的任務放入阻塞隊列則會不斷報出任務拒絕信息。
takeCount
:線程池須要執行的任務數量
completedTaskCount
:線程池在運行過程當中已完成的任務數量
largestPoolSize
:線程池曾經建立過的最大線程數量
getPoolSize
:線程池的線程數量
getActiveCount
:獲取活動的線程數目
能夠經過擴展線程池進行監控,重寫beforExecute()
、afterExecute()
和terminated()
方法。
到這裏爲止,應該要掌握下面的知識,若是你能很流暢地回答,那麼恭喜你,線程池的基本原理算是過關了
總結不易,若是閱讀完這篇文章對你有小小的收穫,你的一個小小的點贊能讓我高興一成天!
巨人的肩膀:
《Java併發編程的藝術》