面試官:說說線程池的工作原理?

線程池的底層是基於線程和任務隊列來實現的,創建線程池的創建方式通常有以下兩種:

  1. 普通 Java 項目,使用 ThreadPoolExecutor 來創建線程池,這點《阿里巴巴 Java 開發手冊》中也有說明,如下圖所示:

  1. 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;
    }
}
  1. 線程池工作流程

當有任務來了之後,線程池的執行流程是這樣的:

  1. 先判斷當前線程數是否大於核心線程數,如果結果爲 false,則新建線程並執行任務。

  2. 如果大於核心線程數,則判斷任務隊列是否已滿,如果結果爲 false,則把任務添加到任務隊列中等待線程執行。

  3. 如果任務隊列已滿,則判斷當前線程數量是否超過最大線程數,如果結果爲 false,則新建線程執行此任務。

  4. 如果超過最大線程數,則將執行線程池的拒絕策略。

如下圖所示:

  1. 拒絕策略

當線程池無法接受新任務時,會觸發拒絕策略,內置的拒絕策略有四種:

  1. AbortPolicy:默認策略,直接拋出 RejectedExecutionException 異常。

  2. CallerRunsPolicy:由調用者線程執行任務。

  3. DiscardPolicy:默默地丟棄任務,沒有任何異常拋出。

  4. 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