之前的博客里有寫過一點線程池吠架,但是只是蜻蜓點水式的談了一下,恰巧前段時間在工作中有了線程池的使用經(jīng)驗搂鲫,而且線程池的優(yōu)化又是一個比較有挑戰(zhàn)的難題傍药,所以這里借著實戰(zhàn)經(jīng)驗結合原理來一篇線程池的總結文章。
1.為什么要用線程池魂仍?
線程池解決的核心問題就是資源管理問題拐辽。在并發(fā)環(huán)境下,系統(tǒng)不能夠確定在任意時刻中擦酌,有多少任務需要執(zhí)行俱诸,有多少資源需要投入。這種不確定性將帶來以下若干問題:
- 頻繁申請/銷毀資源和調(diào)度資源赊舶,將帶來額外的消耗睁搭,可能會非常巨大。
- 對資源無限申請缺少抑制手段笼平,易引發(fā)系統(tǒng)資源耗盡的風險园骆。
- 系統(tǒng)無法合理管理內(nèi)部的資源分布,會降低系統(tǒng)的穩(wěn)定性出吹。
為解決資源分配這個問題遇伞,線程池采用了“池化”(Pooling)思想。池化捶牢,顧名思義鸠珠,就是將資源統(tǒng)一在一起管理的一種思想。
2.線程池的核心參數(shù)
Java中的線程池核心實現(xiàn)類是ThreadPoolExecutor秋麸,ThreadPoolExecutor實現(xiàn)的頂層接口是Executor渐排,頂層接口Executor提供了一種思想:將任務提交和任務執(zhí)行進行解耦。用戶無需關注如何創(chuàng)建線程灸蟆,如何調(diào)度線程來執(zhí)行任務驯耻,用戶只需提供Runnable對象,將任務的運行邏輯提交到執(zhí)行器(Executor)中炒考,由Executor框架完成線程的調(diào)配和任務的執(zhí)行部分可缚。ThreadPoolExecutor,主要構造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize:核心線程大小斋枢,當提交一個任務到線程池時帘靡,線程池會創(chuàng)建一個線程來執(zhí)行任務,即使有其他空閑線程可以處理任務也會創(chuàng)新線程瓤帚,等到工作的線程數(shù)大于核心線程數(shù)時就不會在創(chuàng)建了描姚。如果調(diào)用了線程池的prestartAllCoreThreads方法涩赢,線程池會提前把核心線程都創(chuàng)造好,并啟動
maximumPoolSize:線程池允許創(chuàng)建的最大線程數(shù)轩勘。如果隊列滿了筒扒,并且以創(chuàng)建的線程數(shù)小于最大線程數(shù),則線程池會再創(chuàng)建新的線程執(zhí)行任務绊寻。如果我們使用了無界隊列花墩,那么所有的任務會加入隊列,這個參數(shù)就沒有什么效果了
keepAliveTime:線程池的工作線程空閑后榛斯,保持存活的時間观游。如果沒有任務處理了搂捧,有些線程會空閑驮俗,空閑的時間超過了這個值,會被回收掉允跑。如果任務很多王凑,并且每個任務的執(zhí)行時間比較短,避免線程重復創(chuàng)建和回收聋丝,可以調(diào)大這個時間索烹,提高線程的利用率。
unit:keepAliveTIme的時間單位弱睦,可以選擇的單位有天百姓、小時、分鐘况木、毫秒垒拢、微妙、千分之一毫秒和納秒火惊。類型是一個枚舉java.util.concurrent.TimeUnit求类,這個枚舉也經(jīng)常使用,有興趣的可以看一下其源碼
workQueue:工作隊列屹耐,用于緩存待處理任務的阻塞隊列尸疆,常見的有4種,后面有介紹
threadFactory:線程池中創(chuàng)建線程的工廠惶岭,可以通過線程工廠給每個創(chuàng)建出來的線程設置更有意義的名字
handler:飽和策略寿弱,當線程池無法處理新來的任務了,那么需要提供一種策略處理提交的新任務按灶,默認有4種策略症革,文章后面會提到
線程池的簡單使用示例代碼:
public class Demo1 {
static ThreadPoolExecutor executor = new ThreadPoolExecutor(3,
5,
10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
int j = i;
String taskName = "任務" + j;
executor.execute(() -> {
//模擬任務內(nèi)部處理耗時
try {
TimeUnit.SECONDS.sleep(j);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + taskName + "處理完畢");
});
}
//關閉線程池
executor.shutdown();
}
}
3、任務調(diào)度流程
- 首先檢測線程池運行狀態(tài)兆衅,如果不是RUNNING地沮,則直接拒絕嗜浮,線程池要保證在RUNNING的狀態(tài)下執(zhí)行任務。
- 如果workerCount < corePoolSize摩疑,則創(chuàng)建并啟動一個線程來執(zhí)行新提交的任務危融。
- 如果workerCount >= corePoolSize,且線程池內(nèi)的阻塞隊列未滿雷袋,則將任務添加到該阻塞隊列中吉殃。
- 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且線程池內(nèi)的阻塞隊列已滿楷怒,則創(chuàng)建并啟動一個線程來執(zhí)行新提交的任務蛋勺。
- 如果workerCount >= maximumPoolSize,并且線程池內(nèi)的阻塞隊列已滿, 則根據(jù)拒絕策略來處理該任務, 默認的處理方式是直接拋異常鸠删。
4.線程池中常見5種工作隊列
任務太多的時候抱完,工作隊列用于暫時緩存待處理的任務,JDK中常見的5種阻塞隊列:
- ArrayBlockingQueue:是一個基于數(shù)組結構的有界阻塞隊列刃泡,此隊列按照先進先出原則對元素進行排序
- LinkedBlockingQueue:是一個基于鏈表結構的阻塞隊列巧娱,此隊列按照先進先出排序元素,吞吐量通常要高于ArrayBlockingQueue烘贴。靜態(tài)工廠方法Executors.newFixedThreadPool使用了這個隊列禁添。
- SynchronousQueue :一個不存儲元素的阻塞隊列,每個插入操作必須等到另外一個線程調(diào)用移除操作桨踪,否則插入操作一直處理阻塞狀態(tài)老翘,吞吐量通常要高于LinkedBlockingQueue,靜態(tài)工廠方法Executors.newCachedThreadPool使用這個隊列
- PriorityBlockingQueue:優(yōu)先級隊列锻离,進入隊列的元素按照優(yōu)先級會進行排序
5.四種常見飽和策略
- AbortPolicy:直接拋出異常
- CallerRunsPolicy:在當前調(diào)用者的線程中運行任務铺峭,即隨丟來的任務,由他自己去處理
- DiscardOldestPolicy:丟棄隊列中最老的一個任務纳账,即丟棄隊列頭部的一個任務逛薇,然后執(zhí)行當前傳入的任務
- DiscardPolicy:不處理,直接丟棄掉疏虫,方法內(nèi)部為空
6.Executors類
Executors類永罚,提供了一系列工廠方法用于創(chuàng)建線程池,返回的線程池都實現(xiàn)了ExecutorService接口卧秘。常用的方法有:
- newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
創(chuàng)建一個單線程的線程池呢袱。這個線程池只有一個線程在工作,也就是相當于單線程串行執(zhí)行所有任務翅敌。如果這個唯一的線程因為異常結束羞福,那么會有一個新的線程來替代它。此線程池保證所有任務的執(zhí)行順序按照任務的提交順序執(zhí)行蚯涮。內(nèi)部使用了無限容量的LinkedBlockingQueue阻塞隊列來緩存任務治专,任務如果比較多卖陵,單線程如果處理不過來,會導致隊列堆滿张峰,引發(fā)OOM泪蔫。
- newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
創(chuàng)建固定大小的線程池。每次提交一個任務就創(chuàng)建一個線程喘批,直到線程達到線程池的最大大小撩荣。線程池的大小一旦達到最大值就會保持不變,在提交新任務饶深,任務將會進入等待隊列中等待餐曹。如果某個線程因為執(zhí)行異常而結束,那么線程池會補充一個新線程敌厘。內(nèi)部使用了無限容量的LinkedBlockingQueue阻塞隊列來緩存任務台猴,任務如果比較多,如果處理不過來额湘,會導致隊列堆滿卿吐,引發(fā)OOM旁舰。
- newCachedThreadPool
public static ExecutorService newCachedThreadPool()
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
創(chuàng)建一個可緩存的線程池锋华。如果線程池的大小超過了處理任務所需要的線程,那么就會回收部分空閑(60秒處于等待任務到來)的線程箭窜,當任務數(shù)增加時毯焕,此線程池又可以智能的添加新線程來處理任務。此線程池的最大值是Integer的最大值(2^31-1)磺樱。內(nèi)部使用了SynchronousQueue同步隊列來緩存任務纳猫,此隊列的特性是放入任務時必須要有對應的線程獲取任務,任務才可以放入成功竹捉。如果處理的任務比較耗時芜辕,任務來的速度也比較快,會創(chuàng)建太多的線程引發(fā)OOM块差。
- newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)
創(chuàng)建一個大小無限的線程池侵续。此線程池支持定時以及周期性執(zhí)行任務的需求。
在《阿里巴巴java開發(fā)手冊》中指出了線程資源必須通過線程池提供憨闰,不允許在應用中自行顯示的創(chuàng)建線程状蜗,這樣一方面是線程的創(chuàng)建更加規(guī)范,可以合理控制開辟線程的數(shù)量鹉动;另一方面線程的細節(jié)管理交給線程池處理轧坎,優(yōu)化了資源的開銷。而線程池不允許使用Executors去創(chuàng)建泽示,而要通過ThreadPoolExecutor方式缸血,這一方面是由于jdk中Executor框架雖然提供了如newFixedThreadPool()蜜氨、newSingleThreadExecutor()、newCachedThreadPool()等創(chuàng)建線程池的方法捎泻,但都有其局限性记劝,不夠靈活;另外由于前面幾種方法內(nèi)部也是通過ThreadPoolExecutor方式實現(xiàn)族扰,使用ThreadPoolExecutor有助于大家明確線程池的運行規(guī)則厌丑,創(chuàng)建符合自己的業(yè)務場景需要的線程池,避免資源耗盡的風險渔呵。
1怒竿、JAVA線程池,這一篇就夠了
2扩氢、JUC中的Executor框架詳解1