面試官:說說線程池的工作原理?
線程池的底層是基於線程和任務隊列來實現的,創建線程池的創建方式通常有以下兩種:
- 普通 Java 項目,使用 ThreadPoolExecutor 來創建線程池,這點《阿里巴巴 Java 開發手冊》中也有說明,如下圖所示:
- Spring 項目中,會使用代碼可讀性更高的 ThreadPoolTaskExecutor 來創建線程池,雖然它的底層也是通過 ThreadPoolExecutor 來實現的,但 ThreadPoolTaskExecutor 可讀性更高,因爲它不需要在構造方法中設置參數,而是通過屬性設置的方式來設置參數的,所以可讀性更高。
Spring 內置的線程池 ThreadPoolTaskExecutor 的使用示例如下:
@Configuration
public class AsyncConfig {
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心線程數
executor.setCorePoolSize(5);
// 最大線程數
executor.setMaxPoolSize(10);
// 隊列容量
executor.setQueueCapacity(20);
// 線程池維護線程所允許的空閒時間
executor.setKeepAliveSeconds(60);
// 線程池對拒絕任務(無線程可用)的處理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 初始化
executor.initialize();
return executor;
}
}
- 線程池工作流程
當有任務來了之後,線程池的執行流程是這樣的:
-
先判斷當前線程數是否大於核心線程數,如果結果爲 false,則新建線程並執行任務。
-
如果大於核心線程數,則判斷任務隊列是否已滿,如果結果爲 false,則把任務添加到任務隊列中等待線程執行。
-
如果任務隊列已滿,則判斷當前線程數量是否超過最大線程數,如果結果爲 false,則新建線程執行此任務。
-
如果超過最大線程數,則將執行線程池的拒絕策略。
如下圖所示:
- 拒絕策略
當線程池無法接受新任務時,會觸發拒絕策略,內置的拒絕策略有四種:
-
AbortPolicy:默認策略,直接拋出 RejectedExecutionException 異常。
-
CallerRunsPolicy:由調用者線程執行任務。
-
DiscardPolicy:默默地丟棄任務,沒有任何異常拋出。
-
DiscardOldestPolicy:嘗試拋棄隊列中最舊的任務,然後重新嘗試提交當前任務。
除了內置的拒絕策略之外,我們還可以設置自定義拒絕策略,它的實現如下:
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
public class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 在這裏處理拒絕的任務
System.err.println("任務被拒絕執行: " + r.toString());
// 可以選擇記錄日誌、拋出自定義異常或採取其他措施
// 例如,可以將任務保存到某個隊列中,稍後再嘗試重新執行
}
}
使用自定義拒絕策略:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 配置線程池參數
int corePoolSize = 5;
int maximumPoolSize = 10;
long keepAliveTime = 60L;
TimeUnit unit = TimeUnit.SECONDS;
int queueCapacity = 25;
// 創建一個阻塞隊列
ArrayBlockingQueue<Runnable> workQueue =
new ArrayBlockingQueue<>(queueCapacity);
// 創建 ThreadPoolExecutor 實例
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
new CustomRejectedExecutionHandler() // 使用自定義的拒絕策略
);
// 提交任務
for (int i = 0; i < 50; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("執行任務: " + taskId + " 由線程 " + Thread.currentThread().getName() + " 執行");
try {
Thread.sleep(1000); // 模擬耗時任務
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 關閉線程池(這不會立即停止所有正在執行的任務)
executor.shutdown();
}
}
課後反思
實際項目中線程池會使用哪種拒絕策略?爲什麼?線程池是通過什麼機制來創建線程的?線程池創建線程時可以設置哪些屬性?
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/j_pfqc3vubGRVLPl6bxp9Q