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é)如下:
- 控制和管理線程;
- 顯著減少CPU閑置時間中狂;
- 提升吞吐能力凫碌。
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ū)別呢说贝?線程池的好處在于:
- 重用存在的線程议惰,減少對象創(chuàng)建、消亡的開銷乡恕,性能佳言询。
- 可有效控制最大并發(fā)線程數(shù),提高系統(tǒng)資源的使用率傲宜,同時避免過多資源競爭运杭,避免堵塞。
- 提供定時執(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
視為線程池真正的接口。
具體介紹下面繼續(xù)梆砸,廢話不多說转质,趕緊的先建個線程池出來溜溜~~~
創(chuàng)建線程池的方法有很多種,我們快馬加鞭帖世,來個最省事兒的休蟹,傻瓜式的創(chuàng)建線程池,不得不先提出Executors
類(注意帶s
)日矫,本類為創(chuàng)建線程池的工具類(了解Java集合的童鞋赂弓,可以類比Collections
類與Collection
接口)。
3.1 Executors類
該類提供了創(chuàng)建線程池的方法哪轿,比較常用的如下:
newSingleThreadExecutor();
newFixedThreadPool(int nThreads);
newCachedThreadPool();
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類
可以看出陪拘,上面四種線程池都基本上是基于ThreadPoolExecutor
和ScheduledThreadPoolExecutor
來實現(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 陡厘,LinkedBlockingQueue 和SynchronousQueue (區(qū)別見注1**) |
threadFactory |
創(chuàng)建新線程所使用的線程工廠√卣迹可以通過線程工廠給每個創(chuàng)建出來的線程設(shè)置自定義名字糙置,主要實現(xiàn)newThread方法即可 |
handler |
參數(shù)maximumPoolSize 達到后丟棄處理的方法,常見的策略有AbortPolicy 是目,CallerRunsPolicy 罢低,DiscardOldestPolicy 和DiscardPolicy (區(qū)別見注2**)∨值眩可以根據(jù)應(yīng)用場景需要來實現(xiàn)RejectedExecutionHandler接口的rejectedExecution方法网持,來實現(xiàn)自定義策略,如記錄日志或持久化不能處理的任務(wù) |
- 注1:
-
ArrayBlockingQueue
: 基于數(shù)組的有界隊列长踊。有助于防止資源耗盡功舀,但較難控制大小,需要考慮池大小和隊列的大小的折衷身弊,大型池小型隊列cpu使用率較高辟汰,但是請求量很大時,可能遇到不可接受的調(diào)度開銷阱佛。小型池大型隊列會降低cpu使用率帖汞,避免頻繁的線程切換導致的系統(tǒng)消耗,但處理速率也就下降了凑术。值得注意的是翩蘸,在生產(chǎn)者放入數(shù)據(jù)和消費者獲取數(shù)據(jù),都是共用同一個鎖對象淮逊,由此也意味著兩者無法真正并行運行催首,這點尤其不同于LinkedBlockingQueue。 -
LinkedBlockingQueue
: 基于鏈表的“無界”隊列泄鹏。實際上具有類似無限大小的容量(Integer.MAX_VALUE)郎任,也可以在構(gòu)造函數(shù)中指定大小。LinkedBlockingQueue之所以能夠高效的處理并發(fā)數(shù)據(jù)备籽,還因為其對于生產(chǎn)者端和消費者端分別采用了獨立的鎖來控制數(shù)據(jù)同步舶治,這也意味著在高并發(fā)的情況下生產(chǎn)者和消費者可以并行地操作隊列中的數(shù)據(jù),以此來提高整個隊列的并發(fā)性能车猬。 -
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:
-
AbortPolicy
: java默認画饥,拋出一個異常:RejectedExecutionException。 -
CallerRunsPolicy
: 如果發(fā)現(xiàn)線程池還在運行浊猾,就直接運行這個線程的run()方法抖甘。 -
DiscardOldestPolicy
: 在線程池的等待隊列中,將隊首任務(wù)拋棄葫慎,使用當前任務(wù)來替換衔彻。 -
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)建線程池的方式有兩種:
- 通過Executors類提供的靜態(tài)工廠方法篓跛,例如:
ExecutorService es = Executors.newFixedThreadPool(nThreads);
- 通過ThreadPoolExecutor來構(gòu)造,例如:
ExecutorService es =
new ThreadPoolExecutor(corePoolSize,maximumPoolSize,
keepAliveTime,timeUnit,workQueue);
其中坦刀,如果沒有特殊要求愧沟,使用第一種方法可以快速構(gòu)建出線程池。如果根據(jù)業(yè)務(wù)不同鲤遥,需要自定義線程池沐寺,第二種方法將給你充分的發(fā)揮空間。
下篇博文將會利用線程池基于Socket實現(xiàn)客戶端->服務(wù)器文件的傳輸盖奈,將會有大量實例代碼混坞。