為什麼要用執行緒池?

2023-05-30 09:00:34

執行緒池是一種管理和複用執行緒資源的機制,它由一個執行緒池管理器和一組工作執行緒組成。執行緒池管理器負責建立和銷燬執行緒池,以及管理執行緒池中的工作執行緒。工作執行緒則負責執行具體的任務。

執行緒池的主要作用是管理和複用執行緒資源,避免了執行緒的頻繁建立和銷燬所帶來的開銷。
執行緒池包含兩個重要的組成部分:

  1. 執行緒池大小:指執行緒池中所能容納的最大執行緒數。執行緒池大小一般根據系統的負載情況和硬體資源來設定。

  2. 工作佇列:用於存放等待執行的任務。當執行緒池中的工作執行緒已經全部被佔用時,新的任務將被加入到工作佇列中等待執行。

執行緒池建立方式

Java中執行緒池的建立方式主要有以下幾種:

  1. 使用 ThreadPoolExecutor 類手動建立:通過 ThreadPoolExecutor 類別建構函式自定義執行緒池的引數,包括核心執行緒數、最大執行緒數、執行緒存活時間、任務佇列等。

  2. 使用 Executors 類提供的工廠方法建立:通過 Executors 類提供的一些靜態工廠方法建立執行緒池,例如 newFixedThreadPool、newSingleThreadExecutor、newCachedThreadPool 等。

  3. 使用 Spring 框架提供的 ThreadPoolTaskExecutor 類:在 Spring 框架中可以通過 ThreadPoolTaskExecutor 類來建立執行緒池。

不同的建立方式適用於不同的場景,通常可以根據實際情況選擇合適的方式建立執行緒池。手動建立 ThreadPoolExecutor 類可以靈活地設定執行緒池引數,但需要對執行緒池的各項引數有一定的瞭解;使用 Executors 工廠方法可以快速建立執行緒池,但可能無法滿足特定的需求,且容易出現記憶體溢位的情況;而 Spring 框架提供的 ThreadPoolTaskExecutor 類則只能在 Spring 框架中使用。

執行緒池使用

ThreadPoolExecutor 使用

ThreadPoolExecutor 執行緒的建立與使用範例如下:

import java.util.concurrent.*;

public class ThreadPoolDemo {

    public static void main(String[] args) {
        // 建立執行緒池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2, // 核心執行緒數
            5, // 最大執行緒數
            60, // 執行緒池中執行緒的空閒時間(單位為秒)
            TimeUnit.SECONDS, // 時間單位
            new ArrayBlockingQueue<>(10) // 任務佇列
        );
        // 提交任務
        for (int i = 1; i <= 20; i++) {
            final int taskId = i;
            executor.submit(() -> {
                System.out.println("執行任務:" + taskId + ",執行緒名稱:" + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000); // 模擬任務執行時間
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        // 關閉執行緒池
        executor.shutdown();
    }
}

在上面的範例中,我們通過 ThreadPoolExecutor 類手動建立了一個執行緒池,設定了執行緒池的核心執行緒數為 2,最大執行緒數為 5,執行緒空閒時間為 60 秒,任務佇列為長度為 10 的 ArrayBlockingQueue。然後我們通過 submit() 方法向執行緒池中提交了 20 個任務,每個任務會在執行時輸出自己的編號和執行執行緒的名稱,然後睡眠1秒鐘模擬任務執行時間。最後,我們呼叫 shutdown() 方法關閉執行緒池。

Executors 使用

import java.util.concurrent.*;

public class ExecutorsDemo {
    public static void main(String[] args) {
        // 建立執行緒池
        ExecutorService executor = Executors.newFixedThreadPool(5);
        // 提交任務
        for (int i = 1; i <= 10; i++) {
            final int taskId = i;
            executor.submit(() -> {
                System.out.println("執行任務:" + taskId + ",執行緒名稱:" + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000); // 模擬任務執行時間
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        // 關閉執行緒池
        executor.shutdown();
    }
}

在上面的範例中,我們使用了 Executors 工廠類中的 newFixedThreadPool() 方法建立了一個執行緒池,該執行緒池有 5 個固定執行緒。然後我們通過 submit() 方法向執行緒池中提交了 10 個任務,每個任務會在執行時輸出自己的編號和執行執行緒的名稱,然後睡眠 1 秒鐘模擬任務執行時間。最後,我們呼叫 shutdown() 方法關閉執行緒池。值得注意的是,使用 Executors 工廠類建立執行緒池雖然非常簡單,但是在實際生產環境中並不推薦,因為這種方式很容易導致執行緒資源被耗盡,從而影響系統的效能和穩定性。

ThreadPoolTaskExecutor 使用

Spring 中 ThreadPoolTaskExecutor 的使用範例如下:

import java.util.concurrent.*;

import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

public class ThreadPoolTaskExecutorDemo {

    public static void main(String[] args) {
        // 建立執行緒池
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2); // 核心執行緒數
        executor.setMaxPoolSize(5); // 最大執行緒數
        executor.setQueueCapacity(10); // 任務佇列長度
        executor.initialize();
        // 提交任務
        for (int i = 1; i <= 20; i++) {
            final int taskId = i;
            executor.submit(() -> {
                System.out.println("執行任務:" + taskId + ",執行緒名稱:" + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000); // 模擬任務執行時間
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        // 關閉執行緒池
        executor.shutdown();
    }
}

在上面的範例中,我們使用了 Spring 框架中的 ThreadPoolTaskExecutor 類建立了一個執行緒池,設定了執行緒池的核心執行緒數為 2,最大執行緒數為 5,任務佇列長度為 10。然後我們通過 submit() 方法向執行緒池中提交了 20 個任務,每個任務會在執行時輸出自己的編號和執行執行緒的名稱,然後睡眠1秒鐘模擬任務執行時間。最後,我們呼叫 shutdown() 方法關閉執行緒池。注意,在使用 Spring 框架中的 ThreadPoolTaskExecutor 類建立執行緒池時,我們需要先呼叫 initialize() 方法進行初始化。

ThreadPoolTaskExecutor 底層還是通過 JDK 中提供的 ThreadPoolExecutor 類實現的。

執行緒池優點詳解

執行緒池相比於執行緒來說,它不需要頻繁的建立和銷燬執行緒,執行緒一旦建立之後,預設情況下就會一直保持線上程池中,等到有任務來了,再用這些已有的執行緒來執行任務,如下圖所示:

優點1:複用執行緒,降低資源消耗

執行緒在建立時要開闢虛擬機器器棧、本地方法棧、程式計數器等私有執行緒的記憶體空間,而銷燬時又要回收這些私有空間資源,如下圖所示:

而執行緒池建立了執行緒之後就會放線上程池中,因此執行緒池相比於執行緒來說,第一個優點就是可以複用執行緒、減低系統資源的消耗

優點2:提高響應速度

執行緒池是複用已有執行緒來執行任務的,而執行緒是在有任務時才新建的,所以相比於執行緒來說,執行緒池能夠更快的響應任務和執行任務。

優點3:管控執行緒數和任務數

執行緒池提供了更多的管理功能,這裡管理功能主要體現在以下兩個方面:

  1. 控制最大並行數:執行緒池可以建立固定的執行緒數,從而避免了無限建立執行緒的問題。當執行緒建立過多時,會導致系統執行變慢,因為 CPU 核數是一定的、能同時處理的任務數也是一定的,而執行緒過多時就會造成執行緒惡意爭搶和執行緒頻繁切換的問題,從而導致程式執行變慢,所以合適的執行緒數才是高效能執行的關鍵。
  2. 控制任務最大數:如果任務無限多,而記憶體又不足的情況下,就會導致程式執行報錯,而執行緒池可以控制最大任務數,當任務超過一定數量之後,就會採用拒絕策略來處理多出的任務,從而保證了系統可以健康的執行。

優點4:更多增強功能

執行緒池相比於執行緒來說提供了更多的功能,比如定時執行和週期執行等功能。

小結

執行緒池是一種管理和複用執行緒資源的機制。相比於執行緒,它具備四個主要優勢:1.複用執行緒,降低了資源消耗;2.提高響應速度;3.提供了管理執行緒數和任務數的能力;4.更多增強功能。

本文已收錄至《Java面試突擊》,專注 Java 面試 100 年,檢視更多:www.javacn.site