面試官:說說線程池的工作原理?
線程池的底層是基于線程和任務(wù)隊(duì)列來實(shí)現(xiàn)的,創(chuàng)建線程池的創(chuàng)建方式通常有以下兩種:
- 普通 Java 項(xiàng)目,使用 ThreadPoolExecutor 來創(chuàng)建線程池,這點(diǎn)《阿里巴巴Java開發(fā)手冊》中也有說明,如下圖所示:
圖片
- Spring 項(xiàng)目中,會(huì)使用代碼可讀性更高的 ThreadPoolTaskExecutor 來創(chuàng)建線程池,雖然它的底層也是通過 ThreadPoolExecutor 來實(shí)現(xiàn)的,但 ThreadPoolTaskExecutor 可讀性更高,因?yàn)樗恍枰跇?gòu)造方法中設(shè)置參數(shù),而是通過屬性設(shè)置的方式來設(shè)置參數(shù)的,所以可讀性更高。
Spring 內(nèi)置的線程池 ThreadPoolTaskExecutor 的使用示例如下:
@Configuration
public class AsyncConfig {
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心線程數(shù)
executor.setCorePoolSize(5);
// 最大線程數(shù)
executor.setMaxPoolSize(10);
// 隊(duì)列容量
executor.setQueueCapacity(20);
// 線程池維護(hù)線程所允許的空閑時(shí)間
executor.setKeepAliveSeconds(60);
// 線程池對拒絕任務(wù)(無線程可用)的處理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 初始化
executor.initialize();
return executor;
}
}
一、線程池工作流程
當(dāng)有任務(wù)來了之后,線程池的執(zhí)行流程是這樣的:
- 先判斷當(dāng)前線程數(shù)是否大于核心線程數(shù),如果結(jié)果為 false,則新建線程并執(zhí)行任務(wù)。
- 如果大于核心線程數(shù),則判斷任務(wù)隊(duì)列是否已滿,如果結(jié)果為 false,則把任務(wù)添加到任務(wù)隊(duì)列中等待線程執(zhí)行。
- 如果任務(wù)隊(duì)列已滿,則判斷當(dāng)前線程數(shù)量是否超過最大線程數(shù),如果結(jié)果為 false,則新建線程執(zhí)行此任務(wù)。
- 如果超過最大線程數(shù),則將執(zhí)行線程池的拒絕策略。
如下圖所示:
圖片
二、拒絕策略
當(dāng)線程池?zé)o法接受新任務(wù)時(shí),會(huì)觸發(fā)拒絕策略,內(nèi)置的拒絕策略有四種:
- AbortPolicy:默認(rèn)策略,直接拋出 RejectedExecutionException 異常。
- CallerRunsPolicy:由調(diào)用者線程執(zhí)行任務(wù)。
- DiscardPolicy:默默地丟棄任務(wù),沒有任何異常拋出。
- DiscardOldestPolicy:嘗試拋棄隊(duì)列中最舊的任務(wù),然后重新嘗試提交當(dāng)前任務(wù)。
除了內(nèi)置的拒絕策略之外,我們還可以設(shè)置自定義拒絕策略,它的實(shí)現(xiàn)如下:
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
public class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 在這里處理拒絕的任務(wù)
System.err.println("任務(wù)被拒絕執(zhí)行: " + r.toString());
// 可以選擇記錄日志、拋出自定義異?;虿扇∑渌胧?
// 例如,可以將任務(wù)保存到某個(gè)隊(duì)列中,稍后再嘗試重新執(zhí)行
}
}
使用自定義拒絕策略:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 配置線程池參數(shù)
int corePoolSize = 5;
int maximumPoolSize = 10;
long keepAliveTime = 60L;
TimeUnit unit = TimeUnit.SECONDS;
int queueCapacity = 25;
// 創(chuàng)建一個(gè)阻塞隊(duì)列
ArrayBlockingQueue<Runnable> workQueue =
new ArrayBlockingQueue<>(queueCapacity);
// 創(chuàng)建 ThreadPoolExecutor 實(shí)例
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
new CustomRejectedExecutionHandler() // 使用自定義的拒絕策略
);
// 提交任務(wù)
for (int i = 0; i < 50; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("執(zhí)行任務(wù): " + taskId + " 由線程 " + Thread.currentThread().getName() + " 執(zhí)行");
try {
Thread.sleep(1000); // 模擬耗時(shí)任務(wù)
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 關(guān)閉線程池(這不會(huì)立即停止所有正在執(zhí)行的任務(wù))
executor.shutdown();
}
}
課后反思
實(shí)際項(xiàng)目中線程池會(huì)使用哪種拒絕策略?為什么?線程池是通過什么機(jī)制來創(chuàng)建線程的?線程池創(chuàng)建線程時(shí)可以設(shè)置哪些屬性?