一文帶你進入Java之ThreadPool

1.簡介

在計算機程序設(shè)計中,線程池是一個在計算機程序中實現(xiàn)并發(fā)執(zhí)行的軟件設(shè)計模式蓄拣。一個線程池保持多個線程等待任務(wù)分配給并發(fā)執(zhí)行的監(jiān)督程序尉咕。通過維護一個線程池的模型,提高性能涤久,例如,對于執(zhí)行時間較短的任務(wù)忍弛,避免了由于頻繁創(chuàng)建和銷毀線程造成的系統(tǒng)消耗响迂。——維基百科

個人理解:線程池就相當于一個處理任務(wù)的線程工廠细疚,里面有很多工人(線程)蔗彤,當任務(wù)來了的時候,可以讓工人立即開始工作(線程執(zhí)行),當任務(wù)處理完了然遏,則可以讓工人休息(sleep)贫途。所以,處理任務(wù)時待侵,我們不用花時間單獨去外面請工人(線程的創(chuàng)建)丢早,完事后不用辭退工人(線程的銷毀),在任務(wù)量比較龐大的時候秧倾,能夠顯著的提高系統(tǒng)的處理能力怨酝。

其作用總結(jié)如下:

  1. 控制和管理線程;
  2. 顯著減少CPU閑置時間中狂;
  3. 提升吞吐能力凫碌。

tips:本講的線程池主要是針對Java自帶的java.util.concurrent包扑毡。

2.使用場景

那什么時候可以考慮上線程池呢胃榕?首先,對于線程瞄摊,可以粗略的分為三個周期:

T1 T2 T3
線程創(chuàng)建 線程執(zhí)行 線程銷毀

T1+T3>>T2時勋又,可以考慮上線程池。對于如何估算各個周期的執(zhí)行時間换帜,可以粗略分析是否是CPU密集型任務(wù)楔壤,如果不是,舉個極端例子:求1+1=?惯驼,那么線程執(zhí)行周期T2就明顯很短蹲嚣,創(chuàng)建和銷毀時間遠大于執(zhí)行時間。此時就可以考慮上線程池了祟牲。

那么隙畜,很多童鞋會有個疑惑,線程池與new Thread()有什么區(qū)別呢说贝?線程池的好處在于:

  1. 重用存在的線程议惰,減少對象創(chuàng)建、消亡的開銷乡恕,性能佳言询。
  2. 可有效控制最大并發(fā)線程數(shù),提高系統(tǒng)資源的使用率傲宜,同時避免過多資源競爭运杭,避免堵塞。
  3. 提供定時執(zhí)行函卒、定期執(zhí)行辆憔、單線程、并發(fā)數(shù)控制等功能。

相反躁愿,new Thread()方法只是單純的創(chuàng)建線程叛本,注重單個線程本身。當啟動多個線程時彤钟,需循環(huán)調(diào)用new Thread()方法来候,耗費大量時間在創(chuàng)建和銷毀線程上。

3. 重要組成部分(類)

Java中線程池的頂級接口是Executor逸雹,里面提供了一個方法void execute(Runnable command);,可以看出來它只是提供了一個線程執(zhí)行的工具類营搅,所以我們更認同地將其子類ExecutorService視為線程池真正的接口。

線程池相關(guān)類

具體介紹下面繼續(xù)梆砸,廢話不多說转质,趕緊的先建個線程池出來溜溜~~~
創(chuàng)建線程池的方法有很多種,我們快馬加鞭帖世,來個最省事兒的休蟹,傻瓜式的創(chuàng)建線程池,不得不先提出Executors類(注意帶s)日矫,本類為創(chuàng)建線程池的工具類(了解Java集合的童鞋赂弓,可以類比Collections類與Collection接口)。

3.1 Executors類

該類提供了創(chuàng)建線程池的方法哪轿,比較常用的如下:

  1. newSingleThreadExecutor();
  2. newFixedThreadPool(int nThreads);
  3. newCachedThreadPool();
  4. newScheduledThreadPool(int corePoolSize);

以上方法都會返回一個線程池盈魁,只是各自的功能不一樣,下面分別介紹各自的實現(xiàn)和使用場景窃诉。

3.1.1 newSingleThreadExecutor();

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
          (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
}

創(chuàng)建一個單線程的線程池杨耙,池中保持單個線程串行執(zhí)行任務(wù),如果線程因異常結(jié)束飘痛,則會創(chuàng)建一個新的線程來替代它珊膜,可以保證所有任務(wù)的執(zhí)行順序按照任務(wù)的提交順序執(zhí)行。

3.1.2 newFixedThreadPool(int nThreads)

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

創(chuàng)建一個固定大小可重用線程的線程池敦冬,任何時候辅搬,頂多有nThreads個線程處于活躍狀態(tài)執(zhí)行任務(wù)。當nThreads個線程滿負荷運轉(zhuǎn)時脖旱,新增的任務(wù)會加到無界隊列里等候堪遂,直到有空閑線程來處理。當線程因異常退出后萌庆,會創(chuàng)建一個新線程來替代溶褪。在某個線程被顯式地關(guān)閉之前,池中的線程將一直存在践险。

3.1.3 newCachedThreadPool();

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

創(chuàng)建一個可根據(jù)需要創(chuàng)建新線程的線程池猿妈,優(yōu)先重用已創(chuàng)建的可用的線程吹菱,該線程池可以顯著的提高程序的性能。當沒有可用的線程時彭则,則會在池中創(chuàng)建新的線程鳍刷。當線程沒有被使用超過60s,則會從池中remove掉俯抖,最低數(shù)量為0输瓜。因此,長時間保持空閑的線程池不會消耗任何資源芬萍。但是尤揣,當出現(xiàn)新任務(wù)時,又要創(chuàng)建一新的工作線程柬祠,又要一定的系統(tǒng)開銷北戏。并且,在使用CachedThreadPool時漫蛔,一定要注意控制任務(wù)的數(shù)量嗜愈,否則,由于大量線程同時運行惩猫,很有會造成系統(tǒng)癱瘓芝硬⊙恋悖可以使用ThreadPoolExecutor構(gòu)造方法(后文會重點講到)創(chuàng)建具有類似屬性但細節(jié)不同(例如超時參數(shù))的線程池轧房。

3.1.4 newScheduledThreadPool(int corePoolSize);

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
public class ScheduledThreadPoolExecutor
    extends ThreadPoolExecutor
    implements ScheduledExecutorService {

    //ScheduledThreadPoolExecutor類的構(gòu)造方法,其余方法和變量略
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
              new DelayedWorkQueue());
    }
 }

創(chuàng)建一個能在指定時間后或周期性地執(zhí)行任務(wù)的線程池绍绘,池中會保持corePoolSize個線程奶镶,即使處于空閑狀態(tài)。

3.2 ThreadPoolExecutor類

可以看出陪拘,上面四種線程池都基本上是基于ThreadPoolExecutorScheduledThreadPoolExecutor來實現(xiàn)的厂镇。在此,我們主要講解前者左刽,了解其構(gòu)造函數(shù)的各個參數(shù)的實際意義捺信。

一切沒有源碼的解釋都是耍流氓。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}
參數(shù)名 作用
corePoolSize 線程池維護的核心線程數(shù)量欠痴。當超過這個范圍的時候迄靠,就需要將新的Runnable放入到等待隊列workQueue中了
maximumPoolSize 線程池維護的最大線程數(shù)量。如果隊列滿了喇辽,并且已創(chuàng)建的線程數(shù)小于最大線程數(shù)掌挚,則線程池會再創(chuàng)建新的線程執(zhí)行任務(wù)。如果使用了無界的workQueue任務(wù)隊列這個參數(shù)就沒效果
keepAliveTime 線程池中超過corePoolSize的線程的存活時間
unit keepAliveTime的時間單位
workQueue 線程池所使用的緩沖隊列菩咨。用于保存等待執(zhí)行的任務(wù)吠式,常見的隊列有ArrayBlockingQueue陡厘,LinkedBlockingQueueSynchronousQueue(區(qū)別見注1**)
threadFactory 創(chuàng)建新線程所使用的線程工廠√卣迹可以通過線程工廠給每個創(chuàng)建出來的線程設(shè)置自定義名字糙置,主要實現(xiàn)newThread方法即可
handler 參數(shù)maximumPoolSize達到后丟棄處理的方法,常見的策略有AbortPolicy是目,CallerRunsPolicy罢低,DiscardOldestPolicyDiscardPolicy(區(qū)別見注2**)∨值眩可以根據(jù)應(yīng)用場景需要來實現(xiàn)RejectedExecutionHandler接口的rejectedExecution方法网持,來實現(xiàn)自定義策略,如記錄日志或持久化不能處理的任務(wù)
  • 注1
  1. ArrayBlockingQueue: 基于數(shù)組的有界隊列长踊。有助于防止資源耗盡功舀,但較難控制大小,需要考慮池大小和隊列的大小的折衷身弊,大型池小型隊列cpu使用率較高辟汰,但是請求量很大時,可能遇到不可接受的調(diào)度開銷阱佛。小型池大型隊列會降低cpu使用率帖汞,避免頻繁的線程切換導致的系統(tǒng)消耗,但處理速率也就下降了凑术。值得注意的是翩蘸,在生產(chǎn)者放入數(shù)據(jù)和消費者獲取數(shù)據(jù),都是共用同一個鎖對象淮逊,由此也意味著兩者無法真正并行運行催首,這點尤其不同于LinkedBlockingQueue。
  2. LinkedBlockingQueue: 基于鏈表的“無界”隊列泄鹏。實際上具有類似無限大小的容量(Integer.MAX_VALUE)郎任,也可以在構(gòu)造函數(shù)中指定大小。LinkedBlockingQueue之所以能夠高效的處理并發(fā)數(shù)據(jù)备籽,還因為其對于生產(chǎn)者端和消費者端分別采用了獨立的鎖來控制數(shù)據(jù)同步舶治,這也意味著在高并發(fā)的情況下生產(chǎn)者和消費者可以并行地操作隊列中的數(shù)據(jù),以此來提高整個隊列的并發(fā)性能车猬。
  3. SynchronousQueue: 無緩沖的等待隊列霉猛,類似于無中介的直接交易,其特點是讀取交替完成诈唬,沒有實際容量韩脏,它將任務(wù)直接提交。對于SynchronousQueue的作用jdk中寫的很清楚:此策略可以避免在處理可能具有內(nèi)部依賴性的請求集時出現(xiàn)鎖铸磅。舉個例子赡矢,如果你的任務(wù)A1杭朱,A2有內(nèi)部關(guān)聯(lián),A1需要先運行吹散,那么先提交A1弧械,再提交A2,當使用SynchronousQueue我們可以保證空民,A1必定先被執(zhí)行刃唐,在A1沒有被執(zhí)行前,A2不可能添加入queue中界轩。
  • 注2
  1. AbortPolicy : java默認画饥,拋出一個異常:RejectedExecutionException。
  2. CallerRunsPolicy : 如果發(fā)現(xiàn)線程池還在運行浊猾,就直接運行這個線程的run()方法抖甘。
  3. DiscardOldestPolicy : 在線程池的等待隊列中,將隊首任務(wù)拋棄葫慎,使用當前任務(wù)來替換衔彻。
  4. DiscardPolicy : 什么也不做。

這一塊不清楚的可以參看Java線程池架構(gòu)(一)原理和源碼解析

tips:下面就是我看完某篇博文收到啟發(fā)偷办,舉一個經(jīng)典的例子艰额,大家可以按照這個思路去理解。

把線程池理解成一個醫(yī)院椒涯,在醫(yī)院成立之初柄沮,醫(yī)生數(shù)量為 0,當有患者時逐工,沒有醫(yī)生來診療患者铡溪,醫(yī)院會去招聘新的醫(yī)生,一旦這些醫(yī)生忙不過來時泪喊,繼續(xù)招聘,直到達到corePoolSize數(shù)量髓涯,停止招聘袒啼。此時的corePoolSize個醫(yī)生為正式員工,即使沒有患者纬纪,也不會辭退他們(銷毀線程)蚓再。

醫(yī)生達到corePoolSize后,當有新患者來就診包各,醫(yī)生忙不過來時劈愚,直接讓他們在候診區(qū)(workQueue)取號等候扭仁,當醫(yī)生看完上一個病人時,會去候診區(qū)叫下一個號進去略号,如果沒有患者,則可以休息揩环。

當患者數(shù)量急劇上升,候診區(qū)座位數(shù)不夠了,這時掏击,醫(yī)院會再去招聘臨時工醫(yī)生,這些臨時工醫(yī)生會讓沒有座位的患者立即就診秩铆,醫(yī)院按需求逐個招聘砚亭,直到達到maximumPoolSize數(shù)量,停止招聘殴玛。

當臨時招聘的醫(yī)生長時間(keepAliveTime)處于空閑狀態(tài)時捅膘,醫(yī)院就會解雇他們,畢竟要額外付工資啊~

4. 總結(jié)

綜上滚粟,文中提到創(chuàng)建線程池的方式有兩種:

  1. 通過Executors類提供的靜態(tài)工廠方法篓跛,例如:
ExecutorService es = Executors.newFixedThreadPool(nThreads);
  1. 通過ThreadPoolExecutor來構(gòu)造,例如:
ExecutorService es =
    new ThreadPoolExecutor(corePoolSize,maximumPoolSize,
        keepAliveTime,timeUnit,workQueue);

其中坦刀,如果沒有特殊要求愧沟,使用第一種方法可以快速構(gòu)建出線程池。如果根據(jù)業(yè)務(wù)不同鲤遥,需要自定義線程池沐寺,第二種方法將給你充分的發(fā)揮空間。

下篇博文將會利用線程池基于Socket實現(xiàn)客戶端->服務(wù)器文件的傳輸盖奈,將會有大量實例代碼混坞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市钢坦,隨后出現(xiàn)的幾起案子究孕,更是在濱河造成了極大的恐慌,老刑警劉巖爹凹,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厨诸,死亡現(xiàn)場離奇詭異,居然都是意外死亡禾酱,警方通過查閱死者的電腦和手機微酬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來颤陶,“玉大人颗管,你說我怎么就攤上這事∽易撸” “怎么了垦江?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長搅方。 經(jīng)常有香客問我比吭,道長绽族,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任梗逮,我火速辦了婚禮项秉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘慷彤。我一直安慰自己娄蔼,他們只是感情好,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布底哗。 她就那樣靜靜地躺著岁诉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪跋选。 梳的紋絲不亂的頭發(fā)上涕癣,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機與錄音前标,去河邊找鬼坠韩。 笑死,一個胖子當著我的面吹牛炼列,可吹牛的內(nèi)容都是我干的只搁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼俭尖,長吁一口氣:“原來是場噩夢啊……” “哼氢惋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起稽犁,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤焰望,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后已亥,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體熊赖,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年陷猫,在試婚紗的時候發(fā)現(xiàn)自己被綠了秫舌。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡绣檬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嫂粟,到底是詐尸還是另有隱情娇未,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布星虹,位于F島的核電站零抬,受9級特大地震影響镊讼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜平夜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一蝶棋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧忽妒,春花似錦玩裙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鸯檬,卻和暖如春决侈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背喧务。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工赖歌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人功茴。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓庐冯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親痊土。 傳聞我的和親對象是個殘疾皇子肄扎,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

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