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