為什么要用線程池
在生產(chǎn)中,基本不會出現(xiàn)手動創(chuàng)建并啟動線程的代碼,因為這樣做有幾個弊端:
- 頻繁創(chuàng)建線程開銷大
- 線程的數(shù)量不可控
- 線程數(shù)過多CPU來回切換開銷大
那么就需要一個對線程集中管理的工具,線程池應(yīng)運而生,使用線程池有如下優(yōu)勢:
- 減少創(chuàng)建新線程的時間
- 重復(fù)利用線程池中的線程,不需要每次創(chuàng)建
- 利用線程池可對線程進行統(tǒng)一的監(jiān)控,分配,調(diào)優(yōu),控制最大并發(fā)數(shù)
- 實現(xiàn)任務(wù)線程隊列緩存策略和拒絕機制
- 隔離線程環(huán)境
JDK埋的坑
JDK為了我們方便使用,提供了幾種創(chuàng)建線程池的方法
ExecutorService executorService = Executors.newCachedThreadPool();
ExecutorService executorService = Executors.newSingleThreadExecutor();
ExecutorService executorService = Executors.newFixedThreadPool(n);
但是!這幾種方法不能用!
這不是我說的,阿里規(guī)范強制要求
往下翻源碼也可以看到
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
//這里隊列的長度是Integer.MAX_VALUE,容易導(dǎo)致OOM
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
核心類ThreadPoolExecutor
通過上面的代碼也能看到,Executors
的幾個創(chuàng)建線程池的方法,底層是調(diào)用了ThreadPoolExecutor
為了避免踩坑,我們也得老老實實用ThreadPoolExecutor創(chuàng)建線程池
七大參數(shù)和底層工作原理
/**
* ThreadPoolExecutor參數(shù)最全的構(gòu)造方法
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
數(shù)了一下,一共7個參數(shù),那這7個參數(shù)分別代表什么?先看圖
- 主線程執(zhí)行execute或submit方法時,先判斷核心池有沒有滿了,也就是判斷正在執(zhí)行的線程數(shù)若大于或等于corePoolSize,執(zhí)行2
- 把任務(wù)放到阻塞隊列中排隊,若隊列滿了,執(zhí)行3
- 臨時創(chuàng)建新的線程,新線程的空閑時間如果超過keepAliveTime就會被銷毀,而且新創(chuàng)建的線程數(shù)+核心池的線程數(shù)不能超過maximumPoolSize,若超過,執(zhí)行4(關(guān)于這點我認為阿里的《碼出高效》那本書說錯了,大家可以自行查看源碼)
- 執(zhí)行相應(yīng)的拒絕策略,拒絕執(zhí)行
由此可知,這7個參數(shù)分別代表:
-
int corePoolSize
核心池大小狈蚤,阻塞隊列未滿時绞吁,最大同時執(zhí)行線程數(shù) -
int maximumPoolSize
最大池大小麸俘,最大a同時執(zhí)行線程數(shù) -
long keepAliveTime
最大池臨時創(chuàng)建的新線程最大空閑時間绽乔,超過則被銷毀 -
TimeUnit unit
最大空閑時間單位 -
BlockingQueue<Runnable> workQueue
阻塞隊列狞山,當(dāng)核心池已滿時码泞,新提交的線程放進阻塞隊列排隊等候 -
ThreadFactory threadFactory
線程工廠吱涉,它用來生產(chǎn)一組相同任務(wù)的結(jié)程。線程池的命名是通過給這個 factory 增加組名前綴來實現(xiàn)的 -
RejectedExecutionHandler handler
拒絕策略捺疼,當(dāng)阻塞隊列和最大池都滿了的時候疏虫,對新提交的線程執(zhí)行拒絕策略,jdk自帶四種拒絕策略- DiscardPolicy:直接丟棄
- DiscardOldestPolicy:丟棄隊列中排隊時間最長的任務(wù)
- CallerRunsPolicy:將任務(wù)交給調(diào)用線程來執(zhí)行
- AbortPolicy:拋異常
調(diào)優(yōu)
這里說一下int maximumPoolSize
參數(shù)的調(diào)優(yōu)帅涂,因為maximumPoolSize是最后一道防線了议薪,提交的線程數(shù)超過maximumPoolSize就執(zhí)行拒絕策略了,所以maximumPoolSize的大小尤其重要媳友。
CPU密集型任務(wù)
CPU密集型任務(wù)的特點是需要大量的運算斯议,CPU全速運行,較少的IO而沒有阻塞醇锚,所以對于CPU密集型任務(wù)哼御,應(yīng)該盡量減少線程切換帶來的消耗,參考配置公式:
IO密集型任務(wù)
IO密集型任務(wù)剛好相反焊唬,CPU占用較少恋昼,大量的阻塞,對于這種情況赶促,應(yīng)該盡量利用CPU的空閑時間液肌,最大線程數(shù)應(yīng)該配置比CPU核心數(shù)多,參考配置公式: