線程池ThreadPoolExecutor實(shí)現(xiàn)原理

原創(chuàng)文章&經(jīng)驗(yàn)總結(jié)&從校招到A廠一路陽(yáng)光一路滄桑

詳情請(qǐng)戳www.codercc.com

image

1. 為什么要使用線程池

在實(shí)際使用中蚜厉,線程是很占用系統(tǒng)資源的贝攒,如果對(duì)線程管理不善很容易導(dǎo)致系統(tǒng)問(wèn)題。因此臼隔,在大多數(shù)并發(fā)框架中都會(huì)使用線程池來(lái)管理線程遂唧,使用線程池管理線程主要有如下好處:

  1. 降低資源消耗如输。通過(guò)復(fù)用已存在的線程和降低線程關(guān)閉的次數(shù)來(lái)盡可能降低系統(tǒng)性能損耗有鹿;
  2. 提升系統(tǒng)響應(yīng)速度旭旭。通過(guò)復(fù)用線程,省去創(chuàng)建線程的過(guò)程葱跋,因此整體上提升了系統(tǒng)的響應(yīng)速度持寄;
  3. 提高線程的可管理性。線程是稀缺資源娱俺,如果無(wú)限制的創(chuàng)建际看,不僅會(huì)消耗系統(tǒng)資源,還會(huì)降低系統(tǒng)的穩(wěn)定性矢否,因此仲闽,需要使用線程池來(lái)管理線程。

2. 線程池的工作原理

當(dāng)一個(gè)并發(fā)任務(wù)提交給線程池僵朗,線程池分配線程去執(zhí)行任務(wù)的過(guò)程如下圖所示:

線程池執(zhí)行流程圖.jpg

從圖可以看出赖欣,線程池執(zhí)行所提交的任務(wù)過(guò)程主要有這樣幾個(gè)階段:

  1. 先判斷線程池中核心線程池所有的線程是否都在執(zhí)行任務(wù)。如果不是验庙,則新創(chuàng)建一個(gè)線程執(zhí)行剛提交的任務(wù)顶吮,否則,核心線程池中所有的線程都在執(zhí)行任務(wù)粪薛,則進(jìn)入第2步悴了;
  2. 判斷當(dāng)前阻塞隊(duì)列是否已滿,如果未滿违寿,則將提交的任務(wù)放置在阻塞隊(duì)列中湃交;否則,則進(jìn)入第3步藤巢;
  3. 判斷線程池中所有的線程是否都在執(zhí)行任務(wù)搞莺,如果沒(méi)有,則創(chuàng)建一個(gè)新的線程來(lái)執(zhí)行任務(wù)掂咒,否則才沧,則交給飽和策略進(jìn)行處理

3. 線程池的創(chuàng)建

創(chuàng)建線程池主要是ThreadPoolExecutor類來(lái)完成,ThreadPoolExecutor的有許多重載的構(gòu)造方法绍刮,通過(guò)參數(shù)最多的構(gòu)造方法來(lái)理解創(chuàng)建線程池有哪些需要配置的參數(shù)温圆。ThreadPoolExecutor的構(gòu)造方法為:

ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

下面對(duì)參數(shù)進(jìn)行說(shuō)明:

  1. corePoolSize:表示核心線程池的大小。當(dāng)提交一個(gè)任務(wù)時(shí)孩革,如果當(dāng)前核心線程池的線程個(gè)數(shù)沒(méi)有達(dá)到corePoolSize岁歉,則會(huì)創(chuàng)建新的線程來(lái)執(zhí)行所提交的任務(wù),即使當(dāng)前核心線程池有空閑的線程嫉戚。如果當(dāng)前核心線程池的線程個(gè)數(shù)已經(jīng)達(dá)到了corePoolSize刨裆,則不再重新創(chuàng)建線程澈圈。如果調(diào)用了prestartCoreThread()或者 prestartAllCoreThreads()彬檀,線程池創(chuàng)建的時(shí)候所有的核心線程都會(huì)被創(chuàng)建并且啟動(dòng)帆啃。
  2. maximumPoolSize:表示線程池能創(chuàng)建線程的最大個(gè)數(shù)。如果當(dāng)阻塞隊(duì)列已滿時(shí)窍帝,并且當(dāng)前線程池線程個(gè)數(shù)沒(méi)有超過(guò)maximumPoolSize的話努潘,就會(huì)創(chuàng)建新的線程來(lái)執(zhí)行任務(wù)。
  3. keepAliveTime:空閑線程存活時(shí)間坤学。如果當(dāng)前線程池的線程個(gè)數(shù)已經(jīng)超過(guò)了corePoolSize疯坤,并且線程空閑時(shí)間超過(guò)了keepAliveTime的話,就會(huì)將這些空閑線程銷毀深浮,這樣可以盡可能降低系統(tǒng)資源消耗压怠。
  4. unit:時(shí)間單位。為keepAliveTime指定時(shí)間單位飞苇。
  5. workQueue:阻塞隊(duì)列菌瘫。用于保存任務(wù)的阻塞隊(duì)列,關(guān)于阻塞隊(duì)列可以看這篇文章布卡∮耆茫可以使用ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue, PriorityBlockingQueue
  6. threadFactory:創(chuàng)建線程的工程類忿等∑苤遥可以通過(guò)指定線程工廠為每個(gè)創(chuàng)建出來(lái)的線程設(shè)置更有意義的名字,如果出現(xiàn)并發(fā)問(wèn)題贸街,也方便查找問(wèn)題原因庵寞。
  7. handler:飽和策略。當(dāng)線程池的阻塞隊(duì)列已滿和指定的線程都已經(jīng)開(kāi)啟薛匪,說(shuō)明當(dāng)前線程池已經(jīng)處于飽和狀態(tài)了皇帮,那么就需要采用一種策略來(lái)處理這種情況。采用的策略有這幾種:
    1. AbortPolicy: 直接拒絕所提交的任務(wù)蛋辈,并拋出RejectedExecutionException異常属拾;
    2. CallerRunsPolicy:只用調(diào)用者所在的線程來(lái)執(zhí)行任務(wù);
    3. DiscardPolicy:不處理直接丟棄掉任務(wù)冷溶;
    4. DiscardOldestPolicy:丟棄掉阻塞隊(duì)列中存放時(shí)間最久的任務(wù)渐白,執(zhí)行當(dāng)前任務(wù)

線程池執(zhí)行邏輯

通過(guò)ThreadPoolExecutor創(chuàng)建線程池后,提交任務(wù)后執(zhí)行過(guò)程是怎樣的逞频,下面來(lái)通過(guò)源碼來(lái)看一看纯衍。execute方法源碼如下:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     *
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     */
    int c = ctl.get();
    //如果線程池的線程個(gè)數(shù)少于corePoolSize則創(chuàng)建新線程執(zhí)行當(dāng)前任務(wù)
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    //如果線程個(gè)數(shù)大于corePoolSize或者創(chuàng)建線程失敗,則將任務(wù)存放在阻塞隊(duì)列workQueue中
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    //如果當(dāng)前任務(wù)無(wú)法放進(jìn)阻塞隊(duì)列中苗胀,則創(chuàng)建新的線程來(lái)執(zhí)行任務(wù)
    else if (!addWorker(command, false))
        reject(command);
}

ThreadPoolExecutor的execute方法執(zhí)行邏輯請(qǐng)見(jiàn)注釋襟诸。下圖為T(mén)hreadPoolExecutor的execute方法的執(zhí)行示意圖:

execute執(zhí)行過(guò)程示意圖.jpg

execute方法執(zhí)行邏輯有這樣幾種情況:

  1. 如果當(dāng)前運(yùn)行的線程少于corePoolSize瓦堵,則會(huì)創(chuàng)建新的線程來(lái)執(zhí)行新的任務(wù);
  2. 如果運(yùn)行的線程個(gè)數(shù)等于或者大于corePoolSize歌亲,則會(huì)將提交的任務(wù)存放到阻塞隊(duì)列workQueue中菇用;
  3. 如果當(dāng)前workQueue隊(duì)列已滿的話,則會(huì)創(chuàng)建新的線程來(lái)執(zhí)行任務(wù)陷揪;
  4. 如果線程個(gè)數(shù)已經(jīng)超過(guò)了maximumPoolSize惋鸥,則會(huì)使用飽和策略RejectedExecutionHandler來(lái)進(jìn)行處理。

需要注意的是悍缠,線程池的設(shè)計(jì)思想就是使用了核心線程池corePoolSize卦绣,阻塞隊(duì)列workQueue和線程池maximumPoolSize,這樣的緩存策略來(lái)處理任務(wù)飞蚓,實(shí)際上這樣的設(shè)計(jì)思想在需要框架中都會(huì)使用滤港。

4. 線程池的關(guān)閉

關(guān)閉線程池,可以通過(guò)shutdownshutdownNow這兩個(gè)方法趴拧。它們的原理都是遍歷線程池中所有的線程溅漾,然后依次中斷線程。shutdownshutdownNow還是有不一樣的地方:

  1. shutdownNow首先將線程池的狀態(tài)設(shè)置為STOP,然后嘗試停止所有的正在執(zhí)行和未執(zhí)行任務(wù)的線程八堡,并返回等待執(zhí)行任務(wù)的列表樟凄;
  2. shutdown只是將線程池的狀態(tài)設(shè)置為SHUTDOWN狀態(tài),然后中斷所有沒(méi)有正在執(zhí)行任務(wù)的線程

可以看出shutdown方法會(huì)將正在執(zhí)行的任務(wù)繼續(xù)執(zhí)行完兄渺,而shutdownNow會(huì)直接中斷正在執(zhí)行的任務(wù)缝龄。調(diào)用了這兩個(gè)方法的任意一個(gè),isShutdown方法都會(huì)返回true挂谍,當(dāng)所有的線程都關(guān)閉成功叔壤,才表示線程池成功關(guān)閉,這時(shí)調(diào)用isTerminated方法才會(huì)返回true口叙。

5. 如何合理配置線程池參數(shù)炼绘?

要想合理的配置線程池,就必須首先分析任務(wù)特性妄田,可以從以下幾個(gè)角度來(lái)進(jìn)行分析:

  1. 任務(wù)的性質(zhì):CPU密集型任務(wù)俺亮,IO密集型任務(wù)和混合型任務(wù)。
  2. 任務(wù)的優(yōu)先級(jí):高疟呐,中和低脚曾。
  3. 任務(wù)的執(zhí)行時(shí)間:長(zhǎng),中和短启具。
  4. 任務(wù)的依賴性:是否依賴其他系統(tǒng)資源本讥,如數(shù)據(jù)庫(kù)連接。

任務(wù)性質(zhì)不同的任務(wù)可以用不同規(guī)模的線程池分開(kāi)處理。CPU密集型任務(wù)配置盡可能少的線程數(shù)量拷沸,如配置Ncpu+1個(gè)線程的線程池色查。IO密集型任務(wù)則由于需要等待IO操作,線程并不是一直在執(zhí)行任務(wù)撞芍,則配置盡可能多的線程秧了,如2xNcpu∏诼混合型的任務(wù)示惊,如果可以拆分好港,則將其拆分成一個(gè)CPU密集型任務(wù)和一個(gè)IO密集型任務(wù)愉镰,只要這兩個(gè)任務(wù)執(zhí)行的時(shí)間相差不是太大,那么分解后執(zhí)行的吞吐率要高于串行執(zhí)行的吞吐率钧汹,如果這兩個(gè)任務(wù)執(zhí)行時(shí)間相差太大丈探,則沒(méi)必要進(jìn)行分解。我們可以通過(guò)Runtime.getRuntime().availableProcessors()方法獲得當(dāng)前設(shè)備的CPU個(gè)數(shù)拔莱。

優(yōu)先級(jí)不同的任務(wù)可以使用優(yōu)先級(jí)隊(duì)列PriorityBlockingQueue來(lái)處理碗降。它可以讓優(yōu)先級(jí)高的任務(wù)先得到執(zhí)行,需要注意的是如果一直有優(yōu)先級(jí)高的任務(wù)提交到隊(duì)列里塘秦,那么優(yōu)先級(jí)低的任務(wù)可能永遠(yuǎn)不能執(zhí)行讼渊。

執(zhí)行時(shí)間不同的任務(wù)可以交給不同規(guī)模的線程池來(lái)處理,或者也可以使用優(yōu)先級(jí)隊(duì)列尊剔,讓執(zhí)行時(shí)間短的任務(wù)先執(zhí)行爪幻。

依賴數(shù)據(jù)庫(kù)連接池的任務(wù),因?yàn)榫€程提交SQL后需要等待數(shù)據(jù)庫(kù)返回結(jié)果须误,如果等待的時(shí)間越長(zhǎng)CPU空閑時(shí)間就越長(zhǎng)挨稿,那么線程數(shù)應(yīng)該設(shè)置越大,這樣才能更好的利用CPU京痢。

并且奶甘,阻塞隊(duì)列最好是使用有界隊(duì)列,如果采用無(wú)界隊(duì)列的話祭椰,一旦任務(wù)積壓在阻塞隊(duì)列中的話就會(huì)占用過(guò)多的內(nèi)存資源臭家,甚至?xí)沟孟到y(tǒng)崩潰。

參考文獻(xiàn)

《Java并發(fā)編程的藝術(shù)》
ThreadPoolExecutor源碼分析方淤,很詳細(xì)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末钉赁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子臣淤,更是在濱河造成了極大的恐慌橄霉,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異姓蜂,居然都是意外死亡按厘,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)钱慢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)逮京,“玉大人,你說(shuō)我怎么就攤上這事束莫±撩蓿” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵览绿,是天一觀的道長(zhǎng)策严。 經(jīng)常有香客問(wèn)我,道長(zhǎng)饿敲,這世上最難降的妖魔是什么妻导? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮怀各,結(jié)果婚禮上倔韭,老公的妹妹穿的比我還像新娘。我一直安慰自己瓢对,他們只是感情好寿酌,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著硕蛹,像睡著了一般醇疼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上妓美,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天僵腺,我揣著相機(jī)與錄音,去河邊找鬼壶栋。 笑死辰如,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的贵试。 我是一名探鬼主播琉兜,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼毙玻!你這毒婦竟也來(lái)了豌蟋?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤桑滩,失蹤者是張志新(化名)和其女友劉穎梧疲,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡幌氮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年缭受,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片该互。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡米者,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宇智,到底是詐尸還是另有隱情蔓搞,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布随橘,位于F島的核電站喂分,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏太防。R本人自食惡果不足惜妻顶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一酸员、第九天 我趴在偏房一處隱蔽的房頂上張望蜒车。 院中可真熱鬧,春花似錦幔嗦、人聲如沸酿愧。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)嬉挡。三九已至,卻和暖如春汇恤,著一層夾襖步出監(jiān)牢的瞬間庞钢,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工因谎, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留基括,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓财岔,卻偏偏與公主長(zhǎng)得像风皿,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子匠璧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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

  • 阿呆的項(xiàng)目經(jīng)理給阿呆分配了一個(gè)統(tǒng)計(jì)點(diǎn)擊量的問(wèn)題桐款。情景是這樣的:每個(gè)廣告位上的創(chuàng)意都可以點(diǎn)擊,點(diǎn)擊過(guò)后會(huì)經(jīng)過(guò)服務(wù)器跳...
    等風(fēng)的豬_閱讀 707評(píng)論 0 3
  • 一.Java中的ThreadPoolExecutor類 java.uitl.concurrent.ThreadPo...
    誰(shuí)在烽煙彼岸閱讀 642評(píng)論 0 0
  • 文章摘要:在業(yè)務(wù)系統(tǒng)中夷恍,線程池框架技術(shù)一直是用來(lái)解決多線程并發(fā)的一種有效方法魔眨。 在JDK中,J.U.C并發(fā)包下的T...
    癲狂俠閱讀 2,084評(píng)論 2 21
  • 前塵世事亦如煙, 峰巒俊美亦如故遏暴; 回望云深煙火處侨艾, 哪知云煙繞幾何…
    飛云冉冉閱讀 226評(píng)論 0 5
  • 深夜無(wú)眠唠梨,突然想寫(xiě)一寫(xiě)身邊的人與事。 找對(duì)了伴侶侥啤,窮一些也會(huì)很快樂(lè)当叭! 我家六樓,久住了一對(duì)夫妻盖灸,很年輕蚁鳖,因?yàn)?..
    雨夜百合12閱讀 321評(píng)論 2 5