Java Executors類的9種建立執行緒池的方法及應用場景分析

威哥爱编程發表於2024-07-09

在Java中,Executors 類提供了多種靜態工廠方法來建立不同型別的執行緒池。在學習執行緒池的過程中,一定避不開Executors類,掌握這個類的使用、原理、使用場景,對於實際專案開發時,運用自如,以下是一些常用的方法,V哥來一一細說:

  1. newCachedThreadPool(): 建立一個可快取的執行緒池,如果執行緒池中的執行緒超過60秒沒有被使用,它們將被終止並從快取中移除。

  2. newFixedThreadPool(int nThreads): 建立一個固定大小的執行緒池,其中 nThreads 指定了執行緒池中執行緒的數量。

  3. newSingleThreadExecutor(): 建立一個單執行緒的執行器,它建立單個工作執行緒來執行任務。

  4. newScheduledThreadPool(int corePoolSize): 建立一個固定大小的執行緒池,它可以根據需要建立新執行緒,但會按照固定延遲執行具有給定初始延遲的任務。

  5. newWorkStealingPool(int parallelism): 建立一個工作竊取執行緒池,它使用多個佇列,每個執行緒都從自己的佇列中竊取任務。

  6. newSingleThreadScheduledExecutor(): 建立一個單執行緒的排程執行器,它可以根據需要建立新執行緒來執行任務。

  7. privilegedThreadFactory(): 建立一個執行緒工廠,用於建立具有特權訪問的執行緒。

  8. defaultThreadFactory(): 建立一個預設的執行緒工廠,用於建立具有非特權訪問的執行緒。

  9. unconfigurableExecutorService(ExecutorService executor): 將給定的 ExecutorService 轉換為不可配置的版本,這樣呼叫者就不能修改它的配置。

這些方法提供了靈活的方式來建立和管理執行緒池,以滿足不同的併發需求,下面 V 哥來一一介紹一下9個方法的實現以及使用場景。

1. newCachedThreadPool()

newCachedThreadPool 方法是 Java java.util.concurrent 包中的 Executors 類的一個靜態工廠方法。這個方法用於建立一個可快取的執行緒池,它能夠根據需要建立新執行緒,並且當執行緒空閒超過一定時間後,執行緒會被終止並從執行緒池中移除。

下面是 newCachedThreadPool 方法的大致實現原理和原始碼分析:

實現原理

  • 執行緒建立: 當提交任務到執行緒池時,如果執行緒池中的執行緒數少於核心執行緒數,會建立新的執行緒來執行任務。
  • 執行緒複用: 如果執行緒池中的執行緒數已經達到核心執行緒數,新提交的任務會被放入任務佇列中等待執行。
  • 執行緒回收: 如果執行緒池中的執行緒在一定時間內(預設是60秒)沒有任務執行,它們會被終止,從而減少資源消耗。

原始碼分析

在 Java 的 java.util.concurrent 包中,Executors 類並沒有直接提供 newCachedThreadPool 的實現,而是透過呼叫 ThreadPoolExecutor 類的建構函式來實現的。以下是 ThreadPoolExecutor 建構函式的呼叫示例:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                   60L, TimeUnit.SECONDS,
                                   new SynchronousQueue<Runnable>());
}

引數解釋:

  • corePoolSize: 核心執行緒數,這裡設定為0,表示執行緒池不會保留任何核心執行緒。
  • maximumPoolSize: 最大執行緒數,這裡設定為 Integer.MAX_VALUE,表示理論上可以建立無限多的執行緒。
  • keepAliveTime: 當執行緒數大於核心執行緒數時,多餘的空閒執行緒能等待新任務的最長時間,這裡設定為60秒。
  • unit: keepAliveTime 引數的時間單位,這裡是秒。
  • workQueue: 一個任務佇列,這裡使用的是 SynchronousQueue,它是一個不儲存元素的阻塞佇列,每個插入操作必須等待一個相應的移除操作。

實現過程

  • 初始化: 當呼叫 newCachedThreadPool 時,會建立一個 ThreadPoolExecutor 例項。
  • 任務提交: 當任務提交給執行緒池時,執行緒池會檢查是否有空閒執行緒可以立即執行任務。
  • 執行緒建立: 如果沒有空閒執行緒,並且當前執行緒數小於 maximumPoolSize,則建立新執行緒執行任務。
  • 任務佇列: 如果當前執行緒數已經達到 maximumPoolSize,則將任務放入 SynchronousQueue 中等待。
  • 執行緒複用: 當一個執行緒執行完任務後,它不會立即終止,而是嘗試從 SynchronousQueue 中獲取新任務。
  • 執行緒回收: 如果執行緒在 keepAliveTime 時間內沒有獲取到新任務,它將被終止。

這種設計使得 newCachedThreadPool 非常適合處理大量短生命週期的任務,因為它可以動態地調整執行緒數量以適應任務負載的變化。然而,由於它可以建立無限多的執行緒,如果沒有適當的任務佇列來控制任務的數量,可能會導致資源耗盡。因此,在使用 newCachedThreadPool 時,需要謹慎考慮任務的特性和系統的資源限制。

使用場景:

適用於執行大量短期非同步任務,尤其是任務執行時間不確定的情況。例如,Web伺服器處理大量併發請求,或者非同步日誌記錄。

2. newFixedThreadPool(int nThreads)

newFixedThreadPool(int nThreads) 是 Java 中 java.util.concurrent 包的 Executors 類的一個靜態工廠方法。這個方法用於建立一個固定大小的執行緒池,它能夠確保執行緒池中始終有固定數量的執行緒在工作。

以下是 newFixedThreadPool 方法的實現原理、原始碼分析以及實現過程:

實現原理

  • 固定執行緒數: 執行緒池中的執行緒數量始終保持為 nThreads。
  • 任務佇列: 提交的任務首先由核心執行緒執行,如果核心執行緒都在忙碌狀態,新任務將被放入一個阻塞佇列中等待執行。
  • 執行緒複用: 執行緒池中的執行緒會重複利用,執行完一個任務後,會立即嘗試從佇列中獲取下一個任務執行。

原始碼分析

newFixedThreadPool 方法是透過呼叫 ThreadPoolExecutor 類的建構函式來實現的。以下是 ThreadPoolExecutor 建構函式的呼叫示例:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(
        nThreads, // 核心執行緒數
        nThreads, // 最大執行緒數
        0L,      // 執行緒空閒時間,這裡設定為0,表示執行緒不會空閒
        TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>() // 使用阻塞佇列來儲存任務
    );
}

引數解釋:

  • corePoolSize: 核心執行緒數,這裡設定為 nThreads,表示執行緒池中始終有 nThreads 個執行緒。
  • maximumPoolSize: 最大執行緒數,這裡也設定為 nThreads,表示執行緒池的執行緒數量不會超過 nThreads。
  • keepAliveTime: 當執行緒數大於核心執行緒數時,多餘的空閒執行緒能等待新任務的最長時間,這裡設定為0,表示如果執行緒池中的執行緒數超過核心執行緒數,這些執行緒將立即終止。
  • unit: keepAliveTime 引數的時間單位,這裡是毫秒。
  • workQueue: 一個任務佇列,這裡使用的是 LinkedBlockingQueue,它是一個基於連結串列的阻塞佇列,可以儲存任意數量的任務。

實現過程

  • 初始化: 當呼叫 newFixedThreadPool 時,會建立一個 ThreadPoolExecutor 例項。
  • 任務提交: 當任務提交給執行緒池時,執行緒池會檢查是否有空閒的核心執行緒可以立即執行任務。
  • 任務佇列: 如果所有核心執行緒都在忙碌狀態,新提交的任務將被放入 LinkedBlockingQueue 中等待。
  • 執行緒複用: 核心執行緒執行完一個任務後,會嘗試從 LinkedBlockingQueue 中獲取新任務繼續執行。
  • 執行緒數量控制: 由於 keepAliveTime 設定為0,當執行緒池中的執行緒數超過核心執行緒數時,這些執行緒會立即終止,從而保證執行緒池中的執行緒數量不會超過 nThreads。

這種設計使得 newFixedThreadPool 非常適合處理大量且持續的任務,因為它可以保證任務以固定的執行緒數量並行執行,同時避免了執行緒數量的無限制增長。然而,由於執行緒池的大小是固定的,如果任務提交的速率超過了執行緒池的處理能力,可能會導致任務在佇列中等待較長時間。因此,在使用 newFixedThreadPool 時,需要根據任務的特性和預期的負載來合理設定 nThreads 的值。

使用場景:

適用於執行大量長期執行的任務,其中執行緒數量需要固定。例如,同時執行多個資料載入或資料處理任務,且希望限制併發數以避免資源過載。

3. newSingleThreadExecutor()

newSingleThreadExecutor 是 Java 中 java.util.concurrent 包的 Executors 類的一個靜態工廠方法,用於建立一個單執行緒的執行器。這個執行器確保所有任務都按照任務提交的順序,在一個執行緒中順序執行。

以下是 newSingleThreadExecutor 方法的實現原理、原始碼分析以及實現過程:

實現原理

  • 單執行緒執行: 執行緒池中只有一個執行緒,所有任務都由這個執行緒順序執行。
  • 任務佇列: 如果這個執行緒在執行任務時有新任務提交,新任務會被放入一個阻塞佇列中等待執行。
  • 執行緒複用: 這個執行緒會重複利用,執行完一個任務後,會立即嘗試從佇列中獲取下一個任務執行。

原始碼分析

newSingleThreadExecutor 方法同樣是透過呼叫 ThreadPoolExecutor 類的建構函式來實現的。以下是 ThreadPoolExecutor 建構函式的呼叫示例:

public static ExecutorService newSingleThreadExecutor() {
    return new ThreadPoolExecutor(
        1, // 核心執行緒數
        1, // 最大執行緒數
        0L, TimeUnit.MILLISECONDS, // 執行緒空閒時間,這裡設定為0,表示執行緒不會空閒
        new LinkedBlockingQueue<Runnable>() // 使用阻塞佇列來儲存任務
    );
}

引數解釋:

  • corePoolSize: 核心執行緒數,這裡設定為1,表示執行緒池中始終有一個核心執行緒。
  • maximumPoolSize: 最大執行緒數,這裡也設定為1,表示執行緒池的執行緒數量不會超過1。
  • keepAliveTime: 執行緒空閒時間,這裡設定為0,表示如果執行緒空閒,它將立即終止。
  • unit: keepAliveTime 引數的時間單位,這裡是毫秒。
  • workQueue: 一個任務佇列,這裡使用的是 LinkedBlockingQueue,它是一個無界佇列,可以儲存任意數量的任務。

實現過程

  • 初始化: 當呼叫 newSingleThreadExecutor 時,會建立一個 ThreadPoolExecutor 例項。
  • 任務提交: 當任務提交給執行緒池時,如果核心執行緒空閒,則立即執行任務;如果核心執行緒忙碌,則將任務放入 LinkedBlockingQueue 中等待。
  • 順序執行: 由於只有一個執行緒,所有任務都將按照提交的順序被執行。
  • 任務佇列: 如果核心執行緒在執行任務,新提交的任務將被放入 LinkedBlockingQueue 中排隊等待。
  • 執行緒複用: 核心執行緒執行完一個任務後,會嘗試從 LinkedBlockingQueue 中獲取新任務繼續執行。
  • 執行緒數量控制: 由於 keepAliveTime 設定為0,核心執行緒在沒有任務執行時會立即終止。但由於 corePoolSize 和 maximumPoolSize 都為1,執行緒池會立即重新建立一個執行緒。

這種設計使得 newSingleThreadExecutor 非常適合處理需要保證任務順序的場景,例如,當任務之間有依賴關係或者需要按照特定順序執行時。同時,由於只有一個執行緒,這也避免了多執行緒環境下的併發問題。然而,由於只有一個執行緒執行任務,這也限制了並行處理的能力,如果任務執行時間較長,可能會導致後續任務等待較長時間。因此,在使用 newSingleThreadExecutor 時,需要根據任務的特性和對順序的要求來決定是否適用。

使用場景:

適用於需要保證任務順序執行的場景,例如,順序處理佇列中的訊息或事件。也適用於需要單個後臺執行緒持續處理週期性任務的情況。

4. newScheduledThreadPool(int corePoolSize)

newScheduledThreadPool 是 Java 中 java.util.concurrent 包的 Executors 類的一個靜態工廠方法,用於建立一個固定大小的執行緒池,這個執行緒池支援定時以及週期性的任務執行。

以下是 newScheduledThreadPool 方法的實現原理、原始碼分析以及實現過程:

實現原理

  • 定時任務: 執行緒池能夠按照指定的延遲執行任務,或者以固定間隔週期性地執行任務。
  • 固定執行緒數: 執行緒池中的執行緒數量被限制為 corePoolSize 指定的大小。
  • 任務佇列: 任務首先由核心執行緒執行,如果核心執行緒都在忙碌狀態,新任務將被放入一個延遲任務佇列中等待執行。

原始碼分析

newScheduledThreadPool 方法是透過呼叫 ScheduledThreadPoolExecutor 類的建構函式來實現的。以下是 ScheduledThreadPoolExecutor 建構函式的呼叫示例:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

這裡的 ScheduledThreadPoolExecutor 是 ThreadPoolExecutor 的一個子類,專門為執行定時任務設計。ScheduledThreadPoolExecutor 建構函式的引數 corePoolSize 定義了執行緒池中核心執行緒的數量。

ScheduledThreadPoolExecutor 內部使用了一個 DelayedWorkQueue 作為任務佇列,這個佇列能夠按照任務的預定執行時間對任務進行排序。

實現過程

  • 初始化: 當呼叫 newScheduledThreadPool 時,會建立一個 ScheduledThreadPoolExecutor 例項。
  • 任務提交: 當任務提交給執行緒池時,執行緒池會根據任務的預定執行時間,將任務放入 DelayedWorkQueue 中。
  • 任務排程: 執行緒池中的執行緒會從 DelayedWorkQueue 中獲取任務,如果任務的執行時間已經到達,執行緒將執行該任務。
  • 執行緒複用: 執行完一個任務的執行緒會再次嘗試從 DelayedWorkQueue 中獲取下一個任務。
  • 執行緒數量控制: 如果任務佇列中的任務數量超過了核心執行緒能夠處理的範圍,ScheduledThreadPoolExecutor 會建立新的執行緒來幫助處理任務,直到達到 corePoolSize 指定的最大執行緒數。

特點

  • ScheduledThreadPoolExecutor 允許設定一個執行緒工廠,用於建立具有特定屬性的執行緒。
  • 它還允許設定一個 RejectedExecutionHandler,當任務無法被接受時(例如,執行緒池關閉或任務佇列已滿),這個處理器會被呼叫。
  • 與 ThreadPoolExecutor 不同,ScheduledThreadPoolExecutor 的 shutdown 和 shutdownNow 方法不會等待延遲任務執行完成。

使用 newScheduledThreadPool 建立的執行緒池非常適合需要執行定時任務的場景,例如,定期執行的後臺任務、定時檢查等。然而,由於它是基於固定大小的執行緒池,所以在高負載情況下,任務可能會排隊等待執行,這需要在設計時考慮適當的 corePoolSize 以滿足效能要求。

使用場景:

適用於需要定期執行任務或在將來某個時間點執行任務的場景。例如,定時備份資料、定時傳送提醒等。

5. newWorkStealingPool(int parallelism)

newWorkStealingPool 是 Java 8 中新增的 java.util.concurrent 包的 Executors 類的一個靜態工廠方法。這個方法用於建立一個工作竊取(Work-Stealing)執行緒池,它能夠提高並行任務的執行效率,特別是在多處理器系統上。

實現原理

  • 工作竊取: 在工作竊取執行緒池中,每個執行緒都有自己的任務佇列。當一個執行緒完成自己的任務後,它會嘗試從其他執行緒的任務佇列中“竊取”任務來執行。
  • 並行級別: 執行緒池的大小由 parallelism 引數決定,這個引數通常等於主機上的處理器核心數。
  • 動態調整: 工作竊取執行緒池可以動態地新增或移除執行緒,以適應任務的負載和執行緒的利用率。

原始碼分析

newWorkStealingPool 方法是透過呼叫 ForkJoinPool 類的靜態工廠方法 commonPoolFor 來實現的。以下是 ForkJoinPool 建構函式的呼叫示例:

public static ExecutorService newWorkStealingPool(int parallelism) {
    return new ForkJoinPool(
        parallelism,
        ForkJoinPool.defaultForkJoinWorkerThreadFactory,
        null, // 沒有未處理的異常處理器
        false // 不是一個非同步任務
    );
}

引數解釋:

  • parallelism: 執行緒池的並行級別,即執行緒池中的執行緒數量。
  • ForkJoinPool.defaultForkJoinWorkerThreadFactory: 預設的執行緒工廠,用於建立執行緒。
  • null: 未處理的異常處理器,這裡沒有指定,因此如果任務丟擲未捕獲的異常,它將被傳播到 ForkJoinTask 的呼叫者。
  • false: 表示這不是一個非同步任務。

ForkJoinPool 內部使用了 ForkJoinWorkerThread 來執行任務,並且每個執行緒都有一個 ForkJoinQueue 來儲存任務。

實現過程

  • 初始化: 當呼叫 newWorkStealingPool 時,會建立一個 ForkJoinPool 例項。
  • 任務提交: 當任務提交給執行緒池時,它們會被放入呼叫執行緒的本地佇列中。
  • 任務執行: 每個執行緒首先嚐試執行其本地佇列中的任務。
  • 工作竊取: 如果本地佇列為空,執行緒會嘗試從其他執行緒的佇列中竊取任務來執行。
  • 動態調整: 執行緒池可以根據需要動態地新增或移除執行緒。

特點

  • 工作竊取執行緒池特別適合於工作量不均勻分佈的任務,因為它可以減少空閒時間並提高資源利用率。
  • 它也適用於可分解為多個子任務的平行計算任務,因為可以將任務分解後,再將子任務提交給執行緒池。
  • 由於每個執行緒都有自己的佇列,因此減少了鎖的爭用,提高了併發效能。

使用 newWorkStealingPool 建立的執行緒池非常適合於需要高併發和高吞吐量的場景,尤其是在多處理器系統上。然而,由於工作竊取機制,它可能不適用於任務執行時間非常短或者任務數量非常少的場景,因為竊取任務本身可能會引入額外的開銷。

使用場景:

適用於工作量不均勻或可分解為多個小任務的平行計算任務。例如,影像處理、資料分析等,可以在多核處理器上有效利用所有核心。

6. newSingleThreadScheduledExecutor()

newSingleThreadScheduledExecutor 是 Java 中 java.util.concurrent 包的 Executors 類的一個靜態工廠方法。這個方法用於建立一個單執行緒的排程執行器,它可以安排命令在給定的延遲後執行,或者定期地執行。

以下是 newSingleThreadScheduledExecutor 方法的實現原理、原始碼分析以及實現過程:

實現原理

  • 單執行緒執行: 執行器確保所有任務都在單個執行緒中順序執行,這保證了任務的執行順序。
  • 定時任務: 支援延遲執行和週期性執行任務。
  • 任務佇列: 所有任務首先被放入一個任務佇列中,然後由單執行緒按順序執行。

原始碼分析

newSingleThreadScheduledExecutor 方法是透過呼叫 ScheduledThreadPoolExecutor 類的建構函式來實現的。以下是 ScheduledThreadPoolExecutor 建構函式的呼叫示例:

public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
    return new ScheduledThreadPoolExecutor(1);
}

這裡,ScheduledThreadPoolExecutor 是 ExecutorService 的一個實現,專門為執行定時任務設計。建構函式只有一個引數,即核心執行緒數,這裡設定為1,表示這是一個單執行緒的執行器。

ScheduledThreadPoolExecutor 內部使用了一個 DelayedWorkQueue 作為任務佇列,這個佇列能夠按照任務的預定執行時間對任務進行排序。

實現過程

  • 初始化: 當呼叫 newSingleThreadScheduledExecutor 時,會建立一個 ScheduledThreadPoolExecutor 例項,其核心執行緒數為1。
  • 任務提交: 當任務提交給執行器時,任務會被封裝成 ScheduledFutureTask 或者 RunnableScheduledFuture,然後放入 DelayedWorkQueue 中。
  • 任務排程: 單執行緒會不斷地從 DelayedWorkQueue 中獲取任務,並按照預定的時間執行。如果任務的執行時間已經到達,任務將被執行;如果還沒有到達,執行緒會等待直到執行時間到來。
  • 順序執行: 由於只有一個執行緒,所有任務都將按照它們被提交的順序被執行。
  • 週期性任務: 對於需要週期性執行的任務,執行器會在每次任務執行完畢後,重新計算下一次執行的時間,並再次將任務放入佇列。

特點

  • newSingleThreadScheduledExecutor 建立的執行器非常適合需要保證任務順序的場景,例如,需要按照特定順序執行的任務或者具有依賴關係的任務。
  • 它也適合執行定時任務,如定期執行的維護任務或者後臺任務。
  • 由於只有一個執行緒,這也避免了多執行緒環境下的併發問題,簡化了任務同步和狀態管理。

使用 newSingleThreadScheduledExecutor 建立的執行器可以提供強大的定時任務功能,同時保持任務執行的順序性。然而,由於只有一個執行緒執行任務,這也限制了並行處理的能力,如果任務執行時間較長,可能會導致後續任務等待較長時間。因此,在使用 newSingleThreadScheduledExecutor 時,需要根據任務的特性和對順序的要求來決定是否適用。

使用場景:

適用於需要單個後臺執行緒按計劃執行任務的場景。例如,定時檢查系統狀態、定時執行維護任務等。

7. privilegedThreadFactory()

privilegedThreadFactory 是 Java 中 java.util.concurrent 包的 Executors 類的一個靜態工廠方法,用於建立一個執行緒工廠,該工廠能夠產生具有特權訪問的執行緒。這意味著這些執行緒可以載入系統屬性和庫,並且可以訪問檔案系統。

以下是 privilegedThreadFactory 方法的實現原理、原始碼分析以及實現過程:

實現原理

  • 特權訪問: 建立的執行緒將具有訪問系統資源的許可權,例如,載入系統屬性和庫。
  • 執行緒建立: 執行緒工廠將建立新的執行緒例項,這些執行緒例項將繼承建立它們的執行緒的上下文。

原始碼分析

在 Java 的標準庫中,privilegedThreadFactory 方法的實現細節並未公開,因為它是一個私有方法。然而,我們可以分析其大致工作原理。privilegedThreadFactory 方法的呼叫示例如下:

public static ThreadFactory privilegedThreadFactory() {
    return new PrivilegedThreadFactory();
}

這裡,PrivilegedThreadFactory 是 Executors 類的一個私有靜態內部類,它實現了 ThreadFactory 介面。ThreadFactory 介面定義了一個 newThread(Runnable r) 方法,用於建立新的執行緒。

實現過程

  • 初始化: 當呼叫 privilegedThreadFactory 方法時,會返回一個新的 PrivilegedThreadFactory 例項。
  • 執行緒建立: 當使用這個工廠建立執行緒時,它會呼叫 newThread(Runnable r) 方法。
  • 特權訪問: 在 newThread(Runnable r) 方法的實現中,會使用 AccessController.doPrivileged 方法來確保新建立的執行緒具有特權訪問。
  • 上下文複製: 通常,新執行緒會複製建立它的執行緒的上下文,包括類載入器等。

示例程式碼

雖然我們不能檢視 privilegedThreadFactory 的具體實現,但是我們可以提供一個示例實現,以展示如何建立具有特權訪問的執行緒:

public class PrivilegedThreadFactory implements ThreadFactory {
    @Override
    public Thread newThread(Runnable r) {
        return AccessController.doPrivileged(new PrivilegedAction<>() {
            @Override
            public Thread run() {
                return new Thread(r);
            }
        });
    }
}

在這個示例中,PrivilegedAction 是一個實現了 PrivilegedAction<T> 介面的匿名類,其 run 方法建立了一個新的執行緒。AccessController.doPrivileged 方法用於執行一個特權操作,這裡是為了確保執行緒建立過程中具有必要的許可權。

特點

  • 使用 privilegedThreadFactory 建立的執行緒可以在需要訪問敏感系統資源的情況下使用。
  • 這種執行緒工廠通常用於需要執行特權操作的應用程式,例如,訪問系統屬性或者執行檔案 I/O 操作。

使用 privilegedThreadFactory 可以確保執行緒在執行任務時具有適當的安全許可權,從而避免安全異常。然而,需要注意的是,過度使用特權訪問可能會帶來安全風險,因此在設計應用程式時應謹慎使用。

使用場景:

適用於需要執行緒具有更高許可權來訪問系統資源的場景。例如,需要訪問系統屬性或執行檔案I/O操作的應用程式。

8. defaultThreadFactory()

defaultThreadFactory 是 Java 中 java.util.concurrent 包的 Executors 類的一個靜態工廠方法,用於建立一個預設的執行緒工廠。這個執行緒工廠生成的執行緒沒有特殊的許可權,它們是普通的執行緒,具有標準的訪問許可權。

以下是 defaultThreadFactory 方法的實現原理、原始碼分析以及實現過程:

實現原理

  • 標準執行緒建立: 建立的執行緒工廠將生成具有預設屬性的執行緒。
  • 執行緒名稱: 生成的執行緒具有預設的執行緒名稱字首,通常是 "pool-x-thread-y",其中 x 和 y 是數字。
  • 執行緒優先順序: 執行緒的優先順序設定為 Thread.NORM_PRIORITY,這是 Java 執行緒的預設優先順序。
  • 非守護執行緒: 建立的執行緒不是守護執行緒(daemon threads),它們的存在不會阻止 JVM 退出。

原始碼分析

Java 的 defaultThreadFactory 方法的具體實現細節並未完全公開,因為它是 Executors 類的一個私有靜態方法。但是,我們可以根據 Java 的 ThreadFactory 介面和一些公開的原始碼片段來分析其大致實現。

以下是 defaultThreadFactory 方法的呼叫示例:

public static ThreadFactory defaultThreadFactory() {
    return new DefaultThreadFactory();
}

這裡,DefaultThreadFactory 是 Executors 類的一個私有靜態內部類,它實現了 ThreadFactory 介面。ThreadFactory 介面定義了一個 newThread(Runnable r) 方法,用於建立新的執行緒。

實現過程

  • 初始化: 當呼叫 defaultThreadFactory 方法時,會返回一個新的 DefaultThreadFactory 例項。
  • 執行緒建立: 使用這個工廠建立執行緒時,它會呼叫 newThread(Runnable r) 方法。
  • 設定執行緒名稱: 在 newThread(Runnable r) 方法的實現中,會建立一個新的 Thread 物件,並設定一個預設的執行緒名稱。
  • 設定執行緒組: 新執行緒會被分配到一個預設的執行緒組中。
  • 執行緒優先順序和守護狀態: 執行緒的優先順序設定為預設值,且執行緒不是守護執行緒。

示例程式碼

雖然我們不能檢視 defaultThreadFactory 的具體實現,但是我們可以提供一個示例實現,以展示如何建立具有預設屬性的執行緒:

public class DefaultThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                     poolNumber.getAndIncrement() +
                     "-thread-";
    }

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

在這個示例中,DefaultThreadFactory 使用 AtomicInteger 來確保執行緒池和執行緒編號的唯一性。建立的執行緒名稱具有字首 "pool-x-thread-y",其中 x 和 y 是自增的數字。執行緒不是守護執行緒,且優先順序設定為 Thread.NORM_PRIORITY。

特點

  • 使用 defaultThreadFactory 建立的執行緒工廠生成的執行緒具有標準的 Java 執行緒屬性。
  • 這種執行緒工廠通常用於不需要特殊許可權的應用程式。
  • 由於執行緒不是守護執行緒,它們的存在可以維持 JVM 的執行,直到所有非守護執行緒執行完畢。

使用 defaultThreadFactory 可以確保執行緒在執行任務時具有標準的安全和執行屬性,適合大多數常規用途。然而,如果應用程式需要特殊的執行緒屬性,如守護執行緒或不同的優先順序,可能需要自定義執行緒工廠。

使用場景:

適用於大多數標準應用程式,需要建立具有預設屬性的執行緒。這是大多數 ExecutorService 實現的預設選擇。

9. unconfigurableExecutorService(ExecutorService executor)

unconfigurableExecutorService 是 Java 中 java.util.concurrent 包的 Executors 類的一個靜態工廠方法。這個方法用於建立一個不可配置的 ExecutorService 包裝器,這意味著一旦包裝後的 ExecutorService 被建立,就不能更改其配置,比如不能修改其執行緒池大小或任務佇列等。

以下是 unconfigurableExecutorService 方法的實現原理、原始碼分析以及實現過程:

實現原理

  • 封裝: 將現有的 ExecutorService 封裝在一個不可配置的代理中。
  • 不可修改: 所有修改配置的方法呼叫,如 shutdown, shutdownNow, setCorePoolSize 等,都將丟擲 UnsupportedOperationException。
  • 轉發: 除了配置修改的方法外,其他方法呼叫將被轉發到原始的 ExecutorService。

原始碼分析

unconfigurableExecutorService 方法的具體實現細節並未完全公開,因為它是 Executors 類的一個私有靜態方法。但是,我們可以根據 Java 的 ExecutorService 介面和代理機制來分析其大致實現。

以下是 unconfigurableExecutorService 方法的呼叫示例:

public static ExecutorService unconfigurableExecutorService(ExecutorService executor) {
    return new FinalizableDelegatedExecutorService(executor);
}

這裡,FinalizableDelegatedExecutorService 是 Executors 類的一個私有靜態內部類,它實現了 ExecutorService 介面,並代理了對另一個 ExecutorService 的呼叫。

實現過程

  • 初始化: 當呼叫 unconfigurableExecutorService 方法時,會返回一個新的 FinalizableDelegatedExecutorService 例項,它將原始的 ExecutorService 作為引數。
  • 方法呼叫攔截: 對 FinalizableDelegatedExecutorService 的方法呼叫將首先被攔截。
  • 配置修改攔截: 如果呼叫的方法是用於修改配置的,比如 shutdown 或 shutdownNow,將丟擲 UnsupportedOperationException。
  • 轉發其他呼叫: 對於其他不涉及配置修改的方法呼叫,比如 submit, execute, 將被轉發到原始的 ExecutorService。

示例程式碼

下面V哥來模擬一個示例實現,以展示如何建立一個不可配置的 ExecutorService 代理:

public class UnconfigurableExecutorService implements ExecutorService {
    private final ExecutorService executor;

    public UnconfigurableExecutorService(ExecutorService executor) {
        this.executor = executor;
    }

    @Override
    public void shutdown() {
        throw new UnsupportedOperationException("Shutdown not allowed");
    }

    @Override
    public List<Runnable> shutdownNow() {
        throw new UnsupportedOperationException("Shutdown not allowed");
    }

    @Override
    public boolean isShutdown() {
        return executor.isShutdown();
    }

    @Override
    public boolean isTerminated() {
        return executor.isTerminated();
    }

    @Override
    public void execute(Runnable command) {
        executor.execute(command);
    }

    // 其他 ExecutorService 方法的實現,遵循相同的模式
}

在這個示例中,UnconfigurableExecutorService 攔截了 shutdown 和 shutdownNow 方法,並丟擲了異常。其他方法則直接轉發到原始的 ExecutorService。

特點

  • 使用 unconfigurableExecutorService 建立的 ExecutorService 代理確保了執行緒池的配置不能被外部修改。
  • 這可以用於防止意外地更改執行緒池的狀態,提高執行緒池使用的安全性。
  • 除了配置修改的方法外,其他所有方法都保持了原有 ExecutorService 的行為。

使用 unconfigurableExecutorService 可以為現有的 ExecutorService 提供一個安全層,確保它們的狀態不會被意外地更改。這對於在多執行緒環境中共享 ExecutorService 時特別有用。

使用場景:

適用於需要確保執行緒池配置在建立後不被更改的場景。例如,當多個元件共享同一個執行緒池時,可以防止一個元件意外修改配置。

最後

以上是V哥在授課過程中整理的關於Executors 9種建立執行緒池的方法及原理分析,分享給大家,希望對正在學習 Java 的你有所幫助,每天分享技術乾貨,歡迎關注威哥愛程式設計,你的鼓勵是V哥技術創作路上的助推器,不喜勿噴,感謝。

相關文章