線程池
合理利用線程池能夠帶來以下好處:
- 降低消耗。通過重復利用已創(chuàng)建的線程降低創(chuàng)建和銷毀線程的消耗;
- 提高響應速度。當任務到達時霹购,任務可以不需要等待線程創(chuàng)建就能立即執(zhí)行;
- 提高穩(wěn)定性句葵。如果無限制的創(chuàng)建線程厕鹃,不僅會消耗系統(tǒng)資源,還會降低系統(tǒng)的穩(wěn)定性乍丈,使用線程池可以進行統(tǒng)一的分配剂碴,調(diào)優(yōu)和監(jiān)控。
為了使線程池的性能達到最優(yōu)轻专,需要注意:
- 任務時同類型的忆矛,并且相互獨立;因為當在線程池中執(zhí)行獨立的任務時,可以隨意的改變線程池的大小和配置催训,這些修改只會對執(zhí)行性能產(chǎn)生影響洽议。而且如果提交的任務依賴其他任務,很可能產(chǎn)生死鎖漫拭。
- 盡量避免執(zhí)行時間較長的任務亚兄。如果執(zhí)行時間很長的任務比較多,除非線程池很大采驻,否則將可能造成“阻塞”审胚,線程池的響應性會變的很糟糕。
ThreadPoolExecutor 框架
在 JUC 中礼旅,ThreadPoolExecutor 實現(xiàn)了一個可配置高性能的線程池膳叨。庫里已經(jīng)封裝了一些常用的的線程池,如果現(xiàn)有的不能滿足實際需求痘系,也可以繼承它進行修改菲嘴。ThreadPoolExecutor 繼承關系如下圖所示:
其中最核心的是 submit 接口,sumbit 內(nèi)部調(diào)用 execute汰翠,無非做了一次封裝龄坪,生成了 Future 對象,通過該對象可以獲取任務結(jié)果复唤。
構(gòu)造函數(shù)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler);
- int corePoolSize
線程池的基本大邢つ:當提交一個任務到線程池時,線程池會創(chuàng)建一個線程來執(zhí)行任務苟穆,即使其他空閑的基本線程能夠執(zhí)行新任務也會創(chuàng)建線程,等到需要執(zhí)行的任務數(shù)大于線程池基本大小時就不再創(chuàng)建唱星。如果調(diào)用了線程池的prestartAllCoreThreads方法雳旅,線程池會提前創(chuàng)建并啟動所有基本線程。
- int maximumPoolSize
線程池最大大小间聊,線程池允許創(chuàng)建的最大線程數(shù)攒盈。如果隊列已滿,并且已創(chuàng)建的線程數(shù)小于最大線程數(shù)哎榴,則線程池會再創(chuàng)建新的線程執(zhí)行任務型豁。值得注意的是如果使用了無界的任務隊列這個參數(shù)就沒什么效果
- long keepAliveTime
線程活動保持時間:線程池的工作線程空閑后,保持存活的時間尚蝌。所以如果任務很多迎变,并且每個任務執(zhí)行的時間比較短,可以調(diào)大這個時間飘言,提高線程的利用率衣形。
- TimeUnit unit
線程活動保持時間的單位
- BlockingQueue<Runnable> workQueue
用于保存等待執(zhí)行的任務的阻塞隊列。一般選用 LinkedBlockingQueue 和 SynchronousQueue姿鸿。
- ThreadFactory threadFactory
用于設置創(chuàng)建線程的工廠谆吴,可以通過線程工廠給每個創(chuàng)建出來的線程設置更有意義的名字倒源,Debug和定位問題時非常又幫助。
- RejectedExecutionHandler handler
當隊列和線程池都滿了句狼,說明線程池處于飽和狀態(tài)笋熬,那么必須采取一種策略處理提交的新任務。這個策略默認情況下是AbortPolicy腻菇,表示無法處理新任務時拋出異常胳螟。以下是 JDK 提供的四種策略
- AbortPolicy:直接拋出異常。
- CallerRunsPolicy:只用調(diào)用者所在線程來運行任務芜繁。
- DiscardOldestPolicy:丟棄隊列里最近的一個任務旺隙,并執(zhí)行當前任務。
- DiscardPolicy:不處理骏令,丟棄掉蔬捷。
參數(shù)與管理機制
ThreadPoolExecutor 根據(jù) corePoolSize, maximumPoolSize榔袋,keepAliveTime 等參數(shù)管理線程周拐。如果線程數(shù)量小于 corePoolSize,則新任務到來都會生成新的線程凰兑。如果數(shù)量大于 corePoolSize妥粟,新任務會被加入到阻塞隊列中去。如果阻塞隊列有界且滿吏够,同時線程數(shù)量還小于 maximumPoolSize勾给,則會生成新的工作線程幫助完成任務。如果很不幸線程數(shù)超過 maximumPoolSize锅知,則按照 RejectedExecutionHandler 處理新到的任務播急。
如果任務完成且線程處于等待狀態(tài),ThreadPoolExecutor 會根據(jù) keepAliveTime 銷毀空閑線程售睹,直到線程數(shù)量不大于 corePoolSize桩警。但是若調(diào)用過 allowCoreThreadTimeOut 接口,corePoolSize 下的線程也會被銷毀昌妹。
線程管理機制如下圖所示:
常用線程池
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
corePoolSize 和 maximumPoolSize 同為 nThreads捶枢, 使用無界阻塞隊列 LinkedBlockingQueue。因此線程池中線程數(shù)量固定飞崖,多余的任務會被加入無界鏈表阻塞隊列烂叔。
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
corePoolSize 和 maximumPoolSize 同為 1, 使用無界阻塞隊列 LinkedBlockingQueue蚜厉。因此線程池中只有 1 條線程长已,多余的任務會被加入無界鏈表阻塞隊列。
newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
corePoolSize 為 0, maximumPoolSize 是 Integer 極大值术瓮,采用 SynchronousQueue 阻塞隊列康聂。也就是說若沒有空閑線程,線程池會為每個任務創(chuàng)建一個線程胞四√裰空閑線程會等待 60 秒接收新任務,若沒有新任務則會被銷毀辜伟。最后所有線程都會被銷毀氓侧。
合理配置
如何合理配置線程池大小,僅供參考导狡。一般需要根據(jù)任務的類型來配置線程池大性枷铩:
如果是CPU密集型任務,就需要盡量壓榨CPU旱捧,參考值可以設為 NCPU+1
如果是IO密集型任務独郎,參考值可以設置為2*NCPU,因為并不是所有任務都是在計算中枚赡,很多是等候 IO氓癌。
當然,這只是一個參考值贫橙,具體的設置還需要根據(jù)實際情況進行調(diào)整贪婉,比如可以先將線程池大小設置為參考值,再觀察任務運行情況和系統(tǒng)負載卢肃、資源利用率來進行適當調(diào)整疲迂。
內(nèi)容來源
Java 并發(fā)編程實戰(zhàn)
http://825635381.iteye.com/blog/2184680
http://www.cnblogs.com/zhanjindong/p/java-concurrent-package-ThreadPoolExecutor.html