前言
從事編程的小伙伴們脐区,必然是要接觸多線程開發(fā)的捐韩,而常見的多線程應(yīng)用程序不過是 new 一個(gè)線程(Thread)并且重寫 run 方法瘟仿,雖然能夠完成任務(wù)脊串,但是 new 的過程以及銷毀的過程總是耗時(shí)又耗費(fèi)資源的凉泄,那么有沒有一個(gè)辦法躏尉,我能夠復(fù)用這些線程,而又不會(huì)導(dǎo)致性能浪費(fèi)呢后众?
線程池
線程池是一種多線程處理形式胀糜,處理過程中將任務(wù)添加到隊(duì)列,然后在創(chuàng)建線程后自動(dòng)啟動(dòng)這些任務(wù)蒂誉。線程池線程都是后臺(tái)線程教藻。每個(gè)線程都使用默認(rèn)的堆棧大小,以默認(rèn)的優(yōu)先級(jí)運(yùn)行右锨,并處于多線程單元中括堤。如果某個(gè)線程在托管代碼中空閑(如正在等待某個(gè)事件),則線程池將插入另一個(gè)輔助線程來使所有處理器保持繁忙。如果所有線程池線程都始終保持繁忙绍移,但隊(duì)列中包含掛起的工作悄窃,則線程池將在一段時(shí)間后創(chuàng)建另一個(gè)輔助線程但線程的數(shù)目永遠(yuǎn)不會(huì)超過最大值。超過最大值的線程可以排隊(duì)蹂窖,但他們要等到其他線程完成后才啟動(dòng)轧抗。
感謝本文思路來源 《深入理解Java之線程池》,作者:海子
Java中的ThreadPoolExecutor類
java.uitl.concurrent.ThreadPoolExecutor類是線程池中最核心的一個(gè)類瞬测,因此如果要透徹地了解Java中的線程池鸦致,必須先了解這個(gè)類。下面我們來看一下ThreadPoolExecutor類的具體實(shí)現(xiàn)源碼涣楷。
在ThreadPoolExecutor類中提供了四個(gè)構(gòu)造方法:
/**
* 非完整代碼,這里為了直觀觀察抗碰,做了簡(jiǎn)化處理
*/
public class ThreadPoolExecutor extends AbstractExecutorService {
...
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
...
}
從上面的代碼可以得知狮斗,ThreadPoolExecutor繼承了AbstractExecutorService類,并提供了四個(gè)構(gòu)造器弧蝇,事實(shí)上碳褒,通過觀察每個(gè)構(gòu)造器的源碼具體實(shí)現(xiàn),發(fā)現(xiàn)前面三個(gè)構(gòu)造器都是調(diào)用的第四個(gè)構(gòu)造器進(jìn)行的初始化工作看疗。
下面解釋下一下構(gòu)造器中各個(gè)參數(shù)的含義:
- corePoolSize:核心池的大小沙峻,這個(gè)參數(shù)跟后面講述的線程池的實(shí)現(xiàn)原理有非常大的關(guān)系。在創(chuàng)建了線程池后两芳,默認(rèn)情況下摔寨,線程池中并沒有任何線程,而是等待有任務(wù)到來才創(chuàng)建線程去執(zhí)行任務(wù)怖辆,除非調(diào)用了prestartAllCoreThreads()或者prestartCoreThread()方法是复,從這2個(gè)方法的名字就可以看出删顶,是預(yù)創(chuàng)建線程的意思,即在沒有任務(wù)到來之前就創(chuàng)建corePoolSize個(gè)線程或者一個(gè)線程淑廊。默認(rèn)情況下逗余,在創(chuàng)建了線程池后,線程池中的線程數(shù)為0季惩,當(dāng)有任務(wù)來之后录粱,就會(huì)創(chuàng)建一個(gè)線程去執(zhí)行任務(wù),當(dāng)線程池中的線程數(shù)目達(dá)到corePoolSize后画拾,就會(huì)把到達(dá)的任務(wù)放到緩存隊(duì)列當(dāng)中啥繁;
- maximumPoolSize:線程池最大線程數(shù),這個(gè)參數(shù)也是一個(gè)非常重要的參數(shù)碾阁,它表示在線程池中最多能創(chuàng)建多少個(gè)線程输虱;
- keepAliveTime:表示線程沒有任務(wù)執(zhí)行時(shí)最多保持多久時(shí)間會(huì)終止。默認(rèn)情況下脂凶,只有當(dāng)線程池中的線程數(shù)大于corePoolSize時(shí)宪睹,keepAliveTime才會(huì)起作用,直到線程池中的線程數(shù)不大于corePoolSize蚕钦,即當(dāng)線程池中的線程數(shù)大于corePoolSize時(shí)亭病,如果一個(gè)線程空閑的時(shí)間達(dá)到keepAliveTime,則會(huì)終止嘶居,直到線程池中的線程數(shù)不超過corePoolSize罪帖。但是如果調(diào)用了allowCoreThreadTimeOut(boolean)方法,在線程池中的線程數(shù)不大于corePoolSize時(shí)邮屁,keepAliveTime參數(shù)也會(huì)起作用整袁,直到線程池中的線程數(shù)為0;
- unit:參數(shù)keepAliveTime的時(shí)間單位佑吝,有7種取值坐昙,在TimeUnit類中有7種靜態(tài)屬性:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小時(shí)
TimeUnit.MINUTES; //分鐘
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //納秒
- workQueue:一個(gè)阻塞隊(duì)列,用來存儲(chǔ)等待執(zhí)行的任務(wù)芋忿,這個(gè)參數(shù)的選擇也很重要炸客,會(huì)對(duì)線程池的運(yùn)行過程產(chǎn)生重大影響,一般來說戈钢,這里的阻塞隊(duì)列有以下幾種選擇:
ArrayBlockingQueue;
PriorityBlockingQueue痹仙;
LinkedBlockingQueue;
SynchronousQueue;
ArrayBlockingQueue和PriorityBlockingQueue使用較少,一般使用LinkedBlockingQueue和SynchronousQueue殉了。線程池的排隊(duì)策略與BlockingQueue有關(guān)开仰。
- threadFactory:線程工廠,主要用來創(chuàng)建線程;
- handler:表示當(dāng)拒絕處理任務(wù)時(shí)的策略抖所,有以下四種取值:
ThreadPoolExecutor.AbortPolicy:丟棄任務(wù)并拋出RejectedExecutionException異常梨州。
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務(wù),但是不拋出異常田轧。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊(duì)列最前面的任務(wù)暴匠,然后重新嘗試執(zhí)行任務(wù)(重復(fù)此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調(diào)用線程處理該任務(wù)
使用示例
知道了 Java 的線程池以來的 ThreadPoolExecutor 類是怎樣的一樣?xùn)|西后,我們?cè)賮斫柚褂闷饋淼木唧w例子加深認(rèn)識(shí)傻粘。
public class Test {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5));
for(int i=0;i<15;i++){
MyTask myTask = new MyTask(i);
executor.execute(myTask);
System.out.println("線程池中線程數(shù)目:"+executor.getPoolSize()+"每窖,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:"+
executor.getQueue().size()+",已執(zhí)行玩別的任務(wù)數(shù)目:"+executor.getCompletedTaskCount());
}
executor.shutdown();
}
}
class MyTask implements Runnable {
private int taskNum;
public MyTask(int num) {
this.taskNum = num;
}
@Override
public void run() {
System.out.println("正在執(zhí)行task "+taskNum);
try {
Thread.currentThread().sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task "+taskNum+"執(zhí)行完畢");
}
}
代碼都 Show 給你看了弦悉,想要加深了解窒典,就自己 Create 一個(gè)項(xiàng)目,敲進(jìn)去運(yùn)行看看結(jié)果吧稽莉!
執(zhí)行結(jié)果:
正在執(zhí)行task 0
線程池中線程數(shù)目:1瀑志,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:0,已執(zhí)行玩別的任務(wù)數(shù)目:0
線程池中線程數(shù)目:2污秆,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:0劈猪,已執(zhí)行玩別的任務(wù)數(shù)目:0
正在執(zhí)行task 1
線程池中線程數(shù)目:3,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:0良拼,已執(zhí)行玩別的任務(wù)數(shù)目:0
正在執(zhí)行task 2
線程池中線程數(shù)目:4战得,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:0,已執(zhí)行玩別的任務(wù)數(shù)目:0
正在執(zhí)行task 3
線程池中線程數(shù)目:5庸推,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:0常侦,已執(zhí)行玩別的任務(wù)數(shù)目:0
正在執(zhí)行task 4
線程池中線程數(shù)目:5,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:1贬媒,已執(zhí)行玩別的任務(wù)數(shù)目:0
線程池中線程數(shù)目:5聋亡,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:2,已執(zhí)行玩別的任務(wù)數(shù)目:0
線程池中線程數(shù)目:5际乘,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:3杀捻,已執(zhí)行玩別的任務(wù)數(shù)目:0
線程池中線程數(shù)目:5,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:4蚓庭,已執(zhí)行玩別的任務(wù)數(shù)目:0
線程池中線程數(shù)目:5,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:5仅仆,已執(zhí)行玩別的任務(wù)數(shù)目:0
線程池中線程數(shù)目:6器赞,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:5,已執(zhí)行玩別的任務(wù)數(shù)目:0
正在執(zhí)行task 10
線程池中線程數(shù)目:7墓拜,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:5港柜,已執(zhí)行玩別的任務(wù)數(shù)目:0
正在執(zhí)行task 11
線程池中線程數(shù)目:8,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:5,已執(zhí)行玩別的任務(wù)數(shù)目:0
正在執(zhí)行task 12
線程池中線程數(shù)目:9夏醉,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:5爽锥,已執(zhí)行玩別的任務(wù)數(shù)目:0
正在執(zhí)行task 13
線程池中線程數(shù)目:10,隊(duì)列中等待執(zhí)行的任務(wù)數(shù)目:5畔柔,已執(zhí)行玩別的任務(wù)數(shù)目:0
正在執(zhí)行task 14
task 0執(zhí)行完畢
task 4執(zhí)行完畢
正在執(zhí)行task 5
task 1執(zhí)行完畢
task 2執(zhí)行完畢
task 3執(zhí)行完畢
正在執(zhí)行task 8
正在執(zhí)行task 7
正在執(zhí)行task 6
正在執(zhí)行task 9
task 10執(zhí)行完畢
task 13執(zhí)行完畢
task 12執(zhí)行完畢
task 11執(zhí)行完畢
task 14執(zhí)行完畢
task 5執(zhí)行完畢
task 6執(zhí)行完畢
task 7執(zhí)行完畢
task 8執(zhí)行完畢
task 9執(zhí)行完畢
從執(zhí)行結(jié)果可以看出氯夷,當(dāng)線程池中線程的數(shù)目大于5時(shí),便將任務(wù)放入任務(wù)緩存隊(duì)列里面靶擦,當(dāng)任務(wù)緩存隊(duì)列滿了之后腮考,便創(chuàng)建新的線程。如果上面程序中玄捕,將for循環(huán)中改成執(zhí)行20個(gè)任務(wù)踩蔚,就會(huì)拋出任務(wù)拒絕異常了。
不過在java doc中枚粘,并不提倡我們直接使用ThreadPoolExecutor馅闽,而是使用Executors類中提供的幾個(gè)靜態(tài)方法來創(chuàng)建線程池:
Executors.newCachedThreadPool(); //創(chuàng)建一個(gè)緩沖池,緩沖池容量大小為Integer.MAX_VALUE
Executors.newSingleThreadExecutor(); //創(chuàng)建容量為1的緩沖池
Executors.newFixedThreadPool(int); //創(chuàng)建固定容量大小的緩沖池
下面是這三個(gè)靜態(tài)方法的具體實(shí)現(xiàn):
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
從它們的具體實(shí)現(xiàn)來看馍迄,它們實(shí)際上也是調(diào)用了ThreadPoolExecutor福也,只不過參數(shù)都已配置好了。
- newFixedThreadPool創(chuàng)建的線程池corePoolSize和maximumPoolSize值是相等的柬姚,它使用的LinkedBlockingQueue拟杉;
- newSingleThreadExecutor將corePoolSize和maximumPoolSize都設(shè)置為1,也使用的LinkedBlockingQueue量承;
- newCachedThreadPool將corePoolSize設(shè)置為0搬设,將maximumPoolSize設(shè)置為Integer.MAX_VALUE,使用的SynchronousQueue撕捍,也就是說來了任務(wù)就創(chuàng)建線程運(yùn)行拿穴,當(dāng)線程空閑超過60秒,就銷毀線程忧风。
實(shí)際中默色,如果Executors提供的三個(gè)靜態(tài)方法能滿足要求,就盡量使用它提供的三個(gè)方法狮腿,因?yàn)樽约喝ナ謩?dòng)配置ThreadPoolExecutor的參數(shù)有點(diǎn)麻煩腿宰,要根據(jù)實(shí)際任務(wù)的類型和數(shù)量來進(jìn)行配置。
另外缘厢,如果ThreadPoolExecutor達(dá)不到要求吃度,可以自己繼承ThreadPoolExecutor類進(jìn)行重寫。
One More Thing
如何合理配置線程池的大小
一般需要根據(jù)任務(wù)的類型來配置線程池大刑颉:
- 如果是CPU密集型任務(wù)椿每,就需要盡量壓榨CPU伊者,參考值可以設(shè)為 NCPU+1
- 如果是IO密集型任務(wù),參考值可以設(shè)置為2*NCPU
當(dāng)然间护,這只是一個(gè)參考值亦渗,具體的設(shè)置還需要根據(jù)實(shí)際情況進(jìn)行調(diào)整,比如可以先將線程池大小設(shè)置為參考值汁尺,再觀察任務(wù)運(yùn)行情況和系統(tǒng)負(fù)載法精、資源利用率來進(jìn)行適當(dāng)調(diào)整。