更新時(shí)間:2023-01-10 15:11:18 來(lái)源:動(dòng)力節(jié)點(diǎn) 瀏覽1375次
什么是線程池
線程池和數(shù)據(jù)庫(kù)連接池非常類似,可以統(tǒng)一管理和維護(hù)線程,減少?zèng)]有必要的開(kāi)銷。
為什么要使用線程池
因?yàn)樵陧?xiàng)目開(kāi)發(fā)過(guò)程中頻繁的開(kāi)啟線程或者停止線程,線程需要重新被CPU從就緒狀態(tài)調(diào)度到運(yùn)行狀態(tài),需要發(fā)生CPU的上下文切換,效率非常低。
線程的生命周期如下圖所示:
線程池有哪些作用
降低資源消耗:通過(guò)池化技術(shù)重復(fù)利用已創(chuàng)建好的線程,降低線程創(chuàng)建和銷毀造成的損耗。
提高響應(yīng)速度:任務(wù)到達(dá)時(shí),無(wú)需等待線程創(chuàng)建即可立即執(zhí)行。
提高線程的可管理性:線程是稀缺資源,如果無(wú)限制創(chuàng)建,不僅會(huì)消耗系統(tǒng)資源,還會(huì)因?yàn)榫€程的不合理分布導(dǎo)致資源調(diào)度失衡,降低系統(tǒng)的穩(wěn)定性。使用線程池可以進(jìn)行統(tǒng)一的分配、調(diào)優(yōu)和監(jiān)控。
提供更多強(qiáng)大的功能:線程池具備可拓展性,允許開(kāi)發(fā)人員向其中增加更多的功能。比如延遲定時(shí)線程池 ScheduledThreadPoolExecutor ,就允許任務(wù)延期執(zhí)行或定期執(zhí)行。
線程池的創(chuàng)建方式
分為以下幾種創(chuàng)建方式:
Executors.newCachedThreadPool():可緩存線程池
Executors.newFixedThreadPool():可定長(zhǎng)度,限制最大線程數(shù)
Executors.newScheduledThreadPool():可定時(shí)線程池
Executors.newSingleThreadExecutor():?jiǎn)卫€程池
底層都是基于 ThreadPoolExecutor 構(gòu)造函數(shù)封裝
如何實(shí)現(xiàn)復(fù)用
本質(zhì)思想:創(chuàng)建一個(gè)線程,不會(huì)立馬體質(zhì)或者銷毀,而是一直實(shí)現(xiàn)復(fù)用。
提前創(chuàng)建固定大小的線程一直保持正在運(yùn)行的狀態(tài)(可能會(huì)非常消耗CPU資源)。
當(dāng)需要線程執(zhí)行任務(wù),將該任務(wù)提交緩存在并發(fā)隊(duì)列中,如果緩存隊(duì)列滿了,則會(huì)執(zhí)行拒絕策略。
正在運(yùn)行的線程從并發(fā)隊(duì)列中獲取任務(wù)執(zhí)行從而實(shí)現(xiàn)線程復(fù)用的問(wèn)題。
線程池底層原理如下圖所示:
線程池核心點(diǎn):復(fù)用機(jī)制
提前創(chuàng)建好固定的線程一直在運(yùn)行狀態(tài)---死循環(huán)實(shí)現(xiàn)。
提交的線程任務(wù)緩存到一個(gè)并發(fā)隊(duì)列集合中,交給我們正在運(yùn)行的線程執(zhí)行。
正在運(yùn)行的線程就從隊(duì)列中獲取該任務(wù)執(zhí)行。
簡(jiǎn)單實(shí)現(xiàn)代碼如下:
/**
* @author zfl_a
* @date 2021/3/20
* @project multi-thread
*/
public class CustExcutors {
// 存放線程任務(wù)
public BlockingDeque<Runnable> runnableList ;
// 停止線程標(biāo)識(shí)位
private volatile Boolean isRun = true ;
/**
* 初始化
* @param dequeSize 隊(duì)列容器大小
* @param threadCount 線程池大小
*/
public CustExcutors(int dequeSize,int threadCount){
runnableList = new LinkedBlockingDeque<>(dequeSize);
for (int i=0;i<threadCount;i++) {
WorkThread workThread = new WorkThread();
workThread.start();
}
}
public void execute(Runnable runnable){
runnableList.offer(runnable);
}
class WorkThread extends Thread {
@Override
public void run (){
// 標(biāo)識(shí)位位true 或者隊(duì)列中有未執(zhí)行完成的任務(wù)
while(isRun || runnableList.size()>0) {
Runnable runnable = runnableList.poll();
// 如果不為空 ,執(zhí)行
if(runnable!=null) {
runnable.run();
}
}
}
}
public static void main(String[] args) {
CustExcutors custExcutors = new CustExcutors(10,2);
for (int x=0;x<10;x++) {
final int i = x ;
custExcutors.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"----"+i);
}
});
}
// 停止線程
custExcutors.isRun=false;
}
}
運(yùn)行結(jié)果:只有兩個(gè)線程在運(yùn)行任務(wù)
ThreadPoolExecutor核心參數(shù)
corePoolSize:核心線程數(shù)量,一直正在保持運(yùn)行的線程。
maximumPoolSize:最大線程數(shù),線程池允許創(chuàng)建的最大線程數(shù)。
keepAliveTime:超出 corePoolSize 后創(chuàng)建的線程存活時(shí)間(當(dāng)超過(guò)核心線程數(shù)后,又沒(méi)有線程任務(wù)執(zhí)行,達(dá)到該存活時(shí)間后,停止該線程)。
unit:keepAliveTime 的時(shí)間單位。
workQueue:任務(wù)隊(duì)列,用于保持待執(zhí)行的任務(wù)。
threadFactory :線程池內(nèi)部創(chuàng)建線程所用的工廠。
handler:任務(wù)無(wú)法執(zhí)行時(shí)的處理器(當(dāng)任務(wù)被拒絕時(shí))。
其他相關(guān)總結(jié)
線程池不會(huì)一直在運(yùn)行狀態(tài)。假設(shè)配置核心線程數(shù) corePoolSize 為2 ,最大線程數(shù) maximumPoolSize 為5,我們可以通過(guò) corePoolSize 核心線程數(shù)后創(chuàng)建的線程的存活時(shí)間例如為60s,在60s內(nèi)沒(méi)有線程任務(wù)執(zhí)行,則會(huì)停止該線程。
線程池底層 ThreadPoolExecutor 底層實(shí)現(xiàn)原理
2.1. 當(dāng)線程數(shù)小于核心線程數(shù)時(shí),創(chuàng)建線程。
2.2. 當(dāng)線程數(shù)大于等于核心線程數(shù),且任務(wù)隊(duì)列未滿時(shí),將任務(wù)放入任務(wù)隊(duì)列。
2.3. 當(dāng)線程數(shù)大于等于核心線程數(shù),且任務(wù)隊(duì)列已滿,有以下兩種情況。
2.3.1. 如果線程數(shù)小于最大線程數(shù),創(chuàng)建線程。
2.3.2. 如果線程數(shù)等于最大線程數(shù),拋出異常,拒絕任務(wù)。
如果隊(duì)列滿了,且任務(wù)總數(shù)>最大線程數(shù)則當(dāng)前線程走拒絕策略。可自定義拒絕異常,將該任務(wù)緩存到Redis、本地文件、mysql中,后期項(xiàng)目啟動(dòng)實(shí)現(xiàn)補(bǔ)償。
拒絕策略有以下幾種:
4.1. AbortPolicy:丟棄任務(wù),拋出運(yùn)行時(shí)異常。
4.2. CallerRunsPolicy:執(zhí)行任務(wù)。
4.3. DiscardPolicy 忽視
4.4. DiscardOldestPolicy:從隊(duì)列中剔除最先進(jìn)入隊(duì)列(最后一個(gè)執(zhí)行)的任務(wù)。
4.5. 實(shí)現(xiàn) RejectedExecutionHandler 接口,可自定義處理器。
如何合理配置參數(shù)
自定義線程池就需要我們自己配置最大線程數(shù) maximumPoolSize ,為了高效的并發(fā)運(yùn)行,這時(shí)需要看我們的業(yè)務(wù)是IO密集型還是CPU密集型。
5.1 CPU密集型:
CPU密集的意思是該任務(wù)需要最大的運(yùn)算,而沒(méi)有阻塞,CPU一直全速運(yùn)行。CPU密集任務(wù)只有在真正的多核CPU上才能得到加速(通過(guò)多線程)。而在單核CPU上,無(wú)論你開(kāi)幾個(gè)模擬的多線程該任務(wù)都不可能得到加速,因?yàn)镃PU總的運(yùn)算能力就那么多。
5.2 IO密集型
IO密集型,即該任務(wù)需要大量的IO,即大量的阻塞。在單線程上運(yùn)行IO密集型的任務(wù)會(huì)導(dǎo)致大量的CPU運(yùn)算能力浪費(fèi)在等待。所以在IO密集型任務(wù)中使用多線程可以大大的加速程序運(yùn)行,即使在單核CPU上這種加速主要就是利用了被浪費(fèi)掉的阻塞時(shí)間。
IO 密集型時(shí),大部分線程都阻塞,故需要多配制線程數(shù)。公式為:
CPU核數(shù)*2
CPU核數(shù)/(1-阻塞系數(shù)) 阻塞系數(shù)在0.8~0.9之間
查看CPU核數(shù):
System.out.println(Runtime.getRuntime().availableProcessors());
以上就是“大廠HR經(jīng)常會(huì)問(wèn)到的Java線程池面試題”,你能回答上來(lái)嗎?如果想要了解更多的Java面試題相關(guān)內(nèi)容,可以關(guān)注動(dòng)力節(jié)點(diǎn)Java官網(wǎng)。
相關(guān)閱讀
0基礎(chǔ) 0學(xué)費(fèi) 15天面授
有基礎(chǔ) 直達(dá)就業(yè)
業(yè)余時(shí)間 高薪轉(zhuǎn)行
工作1~3年,加薪神器
工作3~5年,晉升架構(gòu)
提交申請(qǐng)后,顧問(wèn)老師會(huì)電話與您溝通安排學(xué)習(xí)