上一篇文章講了有關(guān)線程池的一些簡單的用法,這篇文章主要是從源碼的角度進一步帶大家了解線程池的工作流程和工作原理圈暗。
首先先來回顧下如何使用線程池開啟線程
private static void createThreadByThreadPoolExecutor() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
for (int i = 0; i < 10; i++) {
MyThread myThread = new MyThread();
executor.execute(myThread);
}
可以看到其實沒有其它特殊的地方,除了構(gòu)建線程池的代碼筑煮,其它最終要的就是executor.execute(myThread)
行代碼了亿胸。
準備工作
在多線程系列的第一篇文章中提到了線程和進程的狀態(tài)汹想,線程池同樣也有狀態(tài),如下:
- Running: 允許接受新的任務(wù)腺律,并且處理隊列中的任務(wù)
- Shutdown: 不接受新的任務(wù)奕短,但是仍然會處理隊列中的任務(wù)
- Stop: 不接受新的任務(wù),不處理隊列中的任務(wù)匀钧,而且中端在運行的任務(wù)
-
Tidying: 所有的任務(wù)都已經(jīng)終端翎碑,并且工作線程數(shù)歸0,該狀態(tài)下的線程都會調(diào)用
terminated()
函數(shù) -
Terminated: 當
terminated()
函數(shù)調(diào)用完后就進入了此狀態(tài)
首先需要知道4個概念之斯,Worker
杈女、workers
、workQueue
和 task
.
-
在線程池中有個比較重要的類吊圾,那就是
Worker
达椰,可以看到其實現(xiàn)了Runnable
接口(其實就是工作線程),繼承了AbstractQueuedSynchronizer
類(俗稱AQS
项乒,在多線程中是很重要的類) workers
就是Worker
的一個集合啰劲,private final HashSet<Worker> workers = new HashSet<Worker>();
task
:需要執(zhí)行的任務(wù),也就是execute()
中的參數(shù)檀何,實現(xiàn)了Runnable
接口-
workQueue
就是工作隊列蝇裤,就是上一篇文章中線程池構(gòu)造函數(shù)中的工作隊列,里面存儲的就是需要執(zhí)行的任務(wù)频鉴,隊列是實現(xiàn)BlockingQueue
接口的類栓辜,有以下這些實現(xiàn)
execute()
本方法傳進去的類是需要實現(xiàn)Runnable接口的,作為一個command
傳進去
遇到新的任務(wù)后
- 如果工作線程數(shù) < 核心線程數(shù)垛孔,那么直接加1個worker
- 如果線程池是正常的工作狀態(tài)藕甩,并且工作隊列能夠添加任務(wù),此時需要第二輪判斷
- 如果線程池因為某種原因不正常了周荐,并且能夠成功從工作隊列中刪除任務(wù)狭莱,那么直接采取拒絕策略
- 如果此時工作線程數(shù)為0僵娃,此時需要新建一個線程(并且這里創(chuàng)建的是非核心線程)來執(zhí)行這個任務(wù),為什么是null呢腋妙,因為已經(jīng)把任務(wù)放在工作隊列里面了默怨。如果新建的worker的firstTask是該任務(wù)的話,就會重復(fù)執(zhí)行兩次任務(wù)骤素。
- 其它情況也就是正常情況下匙睹,是啥都不干,因為主要目的是把任務(wù)放到工作隊列中
- 如果工作隊列已經(jīng)滿了济竹,則需要判斷是否能夠成功添加一個非核心線程垃僚,如果連非核心線程數(shù)都滿足不了了,就是線程池真的搞不了了规辱,累了,所以也會調(diào)用拒絕策略栽燕。
注意:核心線程和非核心線程只是語義上的說法罕袋,沒有本質(zhì)上的區(qū)別
addworker()
addworker
的作用是檢查是否可以根據(jù)當前池狀態(tài)和給定界限(核心或最大值)添加新線程 ,并且通過第二個參數(shù)來判定是否創(chuàng)建核心線程碍岔,當為true的時候就是核心線程浴讯,反之就是非核心線程。源碼里的注釋如下
來看看代碼具體是如何的
- 一進來就是一個死循環(huán)蔼啦,這個死循環(huán)最主要的目的是確認線程池狀態(tài)是否正常榆纽。如果線程池的狀態(tài)大于SHUTDOWN,也就是處于STOP捏肢、TIDYING或者TERMINATED的時候奈籽,線程池都沒了,還創(chuàng)建worker干啥鸵赫,直接返回fasle衣屏;當線程池處于SHUTDOWN的時候,又得再次判斷:
- 傳進來的任務(wù)如果不為空辩棒,那么就不用添加worker的狼忱,銀行下班了,辦理完現(xiàn)在的顧客就不在辦理了一睁,不然一直來那不是一直不能下班钻弄。
- 傳進來的任務(wù)為空,但是工作隊列為空者吁,同樣不用添加worker窘俺,銀行都沒有任務(wù)了,而且又到點下班了复凳。除了以上3中情況返回false批销,其它情況不做任何反應(yīng)洒闸,往下走。
- 又是一個死循環(huán)均芽,首先得到工作線程數(shù)如果超過了邊界丘逸,比如超過了容量、核心線程數(shù)或者最大線程數(shù)掀宋,就不用添加worker了深纲,銀行實在是辦理不了新的顧客了;當工作線程數(shù)正常的情況下劲妙,通過CAS來增加工作線程數(shù)湃鹊,如果能增加成功就退出最外層循環(huán)。如果增加工作線程失敗镣奋,那就是其它線程增加了該數(shù)量币呵,如果此時線程池的運行狀態(tài)發(fā)生了改變,則重復(fù)外層循環(huán)侨颈,否則就自旋直到成功增加工作線程數(shù)余赢。
- 到這里就可以說基本掃除了障礙,以傳進來的firstTask新建一個Worker哈垢,然后獲取Worker里的線程妻柒。如果線程為null,那么就直接執(zhí)行添加Worker失敗的邏輯耘分,否則就是正常的邏輯举塔。
- 先來看正常的邏輯,拿到鎖求泰,并且開始又一次的獲取線程池的狀態(tài)
- 如果線程是正常運行狀態(tài)央渣,或者說是關(guān)閉狀態(tài)下firstTask仍然為null,此時如果線程是
alive
那么而說明線程已經(jīng)開啟渴频,直接拋出異常痹屹。 - 正常情況下是wokers集合中添加新的worker元素,并且調(diào)整線程池最大值枉氮,設(shè)置workerAdded標志為true志衍。
-
重點 ,當workerAdded為true的時候聊替,開啟工作線程楼肪,也就是代碼中的
t.start()
- 如果線程是正常運行狀態(tài)央渣,或者說是關(guān)閉狀態(tài)下firstTask仍然為null,此時如果線程是
- 再來看失敗的邏輯 ,是在第3點構(gòu)造一個Worker對象惹悄,獲取線程的時候春叫,有可能獲取到的線程為空,這樣其實就是增加worker失敗,返回false暂殖,在返回false之前會觸發(fā)執(zhí)行失敗的邏輯
addWorkerFailed()
邏輯价匠。整個的邏輯是比較簡單的,就不再花費篇幅去闡述呛每。
到這里踩窖,整個addWorker()
的流程是比較清晰的,值得一提的就是第2行代碼中的retry
這個看起來是關(guān)鍵字晨横,但其實不是洋腮,僅僅只是一個類似標志位的東西,可以是retry手形,也可以是abc啥供。
通常是配合for循環(huán)來使用,搭配continue和break可以達到goto的效果库糠。比如continue retry;
就是跳到一開始最外層的for循環(huán)伙狐,break retry;
相當于退出整個循環(huán)。寫一個最簡單的例子大家都能知道了瞬欧。
public class RetryExample {
public static void main(String[] args) {
abc:
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if (j == 2) {
continue abc;
}
if (i == 8) {
break abc;
}
System.out.println("i: " + i + ", j: " + j);
}
}
}
}
輸出結(jié)果如下圖所示
創(chuàng)作不易贷屎,如果對你有幫助,歡迎點贊黍判,收藏和分享啦!