【多線程】線程池源碼(1)

上一篇文章講了有關(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 杈女、workersworkQueuetask.

  • 在線程池中有個比較重要的類吊圾,那就是Worker 达椰,可以看到其實現(xiàn)了Runnable接口(其實就是工作線程),繼承了AbstractQueuedSynchronizer類(俗稱AQS项乒,在多線程中是很重要的類)

    image
  • 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)

    image

execute()

本方法傳進去的類是需要實現(xiàn)Runnable接口的,作為一個command 傳進去

image

遇到新的任務(wù)

  1. 如果工作線程數(shù) < 核心線程數(shù)垛孔,那么直接加1個worker
  2. 如果線程池是正常的工作狀態(tài)藕甩,并且工作隊列能夠添加任務(wù),此時需要第二輪判斷
    1. 如果線程池因為某種原因不正常了周荐,并且能夠成功從工作隊列中刪除任務(wù)狭莱,那么直接采取拒絕策略
    2. 如果此時工作線程數(shù)為0僵娃,此時需要新建一個線程(并且這里創(chuàng)建的是非核心線程)來執(zhí)行這個任務(wù),為什么是null呢腋妙,因為已經(jīng)把任務(wù)放在工作隊列里面了默怨。如果新建的worker的firstTask是該任務(wù)的話,就會重復(fù)執(zhí)行兩次任務(wù)骤素。
    3. 其它情況也就是正常情況下匙睹,是啥都不干,因為主要目的是把任務(wù)放到工作隊列中
  3. 如果工作隊列已經(jīng)滿了济竹,則需要判斷是否能夠成功添加一個非核心線程垃僚,如果連非核心線程數(shù)都滿足不了了,就是線程池真的搞不了了规辱,累了,所以也會調(diào)用拒絕策略栽燕。

注意:核心線程和非核心線程只是語義上的說法罕袋,沒有本質(zhì)上的區(qū)別

addworker()

addworker 的作用是檢查是否可以根據(jù)當前池狀態(tài)和給定界限(核心或最大值)添加新線程 ,并且通過第二個參數(shù)來判定是否創(chuàng)建核心線程碍岔,當為true的時候就是核心線程浴讯,反之就是非核心線程。源碼里的注釋如下

image

來看看代碼具體是如何的

image
  1. 一進來就是一個死循環(huán)蔼啦,這個死循環(huán)最主要的目的是確認線程池狀態(tài)是否正常榆纽。如果線程池的狀態(tài)大于SHUTDOWN,也就是處于STOP捏肢、TIDYING或者TERMINATED的時候奈籽,線程池都沒了,還創(chuàng)建worker干啥鸵赫,直接返回fasle衣屏;當線程池處于SHUTDOWN的時候,又得再次判斷:
    1. 傳進來的任務(wù)如果不為空辩棒,那么就不用添加worker的狼忱,銀行下班了,辦理完現(xiàn)在的顧客就不在辦理了一睁,不然一直來那不是一直不能下班钻弄。
    2. 傳進來的任務(wù)為空,但是工作隊列為空者吁,同樣不用添加worker窘俺,銀行都沒有任務(wù)了,而且又到點下班了复凳。除了以上3中情況返回false批销,其它情況不做任何反應(yīng)洒闸,往下走。
  2. 又是一個死循環(huán)均芽,首先得到工作線程數(shù)如果超過了邊界丘逸,比如超過了容量、核心線程數(shù)或者最大線程數(shù)掀宋,就不用添加worker了深纲,銀行實在是辦理不了新的顧客了;當工作線程數(shù)正常的情況下劲妙,通過CAS來增加工作線程數(shù)湃鹊,如果能增加成功就退出最外層循環(huán)。如果增加工作線程失敗镣奋,那就是其它線程增加了該數(shù)量币呵,如果此時線程池的運行狀態(tài)發(fā)生了改變,則重復(fù)外層循環(huán)侨颈,否則就自旋直到成功增加工作線程數(shù)余赢。
  3. 到這里就可以說基本掃除了障礙,以傳進來的firstTask新建一個Worker哈垢,然后獲取Worker里的線程妻柒。如果線程為null,那么就直接執(zhí)行添加Worker失敗的邏輯耘分,否則就是正常的邏輯举塔。
  4. 先來看正常的邏輯,拿到鎖求泰,并且開始又一次的獲取線程池的狀態(tài)
    1. 如果線程是正常運行狀態(tài)央渣,或者說是關(guān)閉狀態(tài)下firstTask仍然為null,此時如果線程是alive 那么而說明線程已經(jīng)開啟渴频,直接拋出異常痹屹。
    2. 正常情況下是wokers集合中添加新的worker元素,并且調(diào)整線程池最大值枉氮,設(shè)置workerAdded標志為true志衍。
    3. 重點 ,當workerAdded為true的時候聊替,開啟工作線程楼肪,也就是代碼中的t.start()
  5. 再來看失敗的邏輯 ,是在第3點構(gòu)造一個Worker對象惹悄,獲取線程的時候春叫,有可能獲取到的線程為空,這樣其實就是增加worker失敗,返回false暂殖,在返回false之前會觸發(fā)執(zhí)行失敗的邏輯addWorkerFailed() 邏輯价匠。整個的邏輯是比較簡單的,就不再花費篇幅去闡述呛每。
image

到這里踩窖,整個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é)果如下圖所示

image

創(chuàng)作不易贷屎,如果對你有幫助,歡迎點贊黍判,收藏和分享啦!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末篙梢,一起剝皮案震驚了整個濱河市顷帖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌渤滞,老刑警劉巖贬墩,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異妄呕,居然都是意外死亡陶舞,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門绪励,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肿孵,“玉大人,你說我怎么就攤上這事疏魏⊥W觯” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵大莫,是天一觀的道長蛉腌。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么烙丛? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任舅巷,我火速辦了婚禮,結(jié)果婚禮上河咽,老公的妹妹穿的比我還像新娘钠右。我一直安慰自己,他們只是感情好库北,可當我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布爬舰。 她就那樣靜靜地躺著,像睡著了一般寒瓦。 火紅的嫁衣襯著肌膚如雪情屹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天杂腰,我揣著相機與錄音垃你,去河邊找鬼。 笑死喂很,一個胖子當著我的面吹牛惜颇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播少辣,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼凌摄,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了漓帅?” 一聲冷哼從身側(cè)響起锨亏,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎忙干,沒想到半個月后器予,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡捐迫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年乾翔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片施戴。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡反浓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出赞哗,到底是詐尸還是另有隱情勾习,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布懈玻,位于F島的核電站巧婶,受9級特大地震影響乾颁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜艺栈,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一英岭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧湿右,春花似錦诅妹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至丈莺,卻和暖如春划煮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背缔俄。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工弛秋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人俐载。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓蟹略,卻偏偏與公主長得像,于是被迫代替她去往敵國和親遏佣。 傳聞我的和親對象是個殘疾皇子挖炬,可洞房花燭夜當晚...
    茶點故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內(nèi)容