為什麼您可能需要 1 個線程的 ExecutorService?

您可以使用Executors.newSingleThreadExecutor方法創建一個ExecutorService,它帶有一個包含單個線程的池。pool的邏輯如下:

  • 該服務一次只執行一項任務。
  • 如果我們提交N個任務執行,所有N個任務都會被單線程一個接一個地執行。
  • 如果線程被中斷,將創建一個新線程來執行任何剩餘的任務。

讓我們想像一下我們的程序需要以下功能的情況:

我們需要在 30 秒內處理用戶請求,但單位時間內不超過一個請求。

我們創建一個任務類來處理用戶請求:


class Task implements Runnable {
   private final int taskNumber;

   public Task(int taskNumber) {
       this.taskNumber = taskNumber;
   }

   @Override
   public void run() {
       try {
           Thread.sleep(1000);
       } catch (InterruptedException ignored) {
       }
       System.out.printf("Processed request #%d on thread id=%d\\n", taskNumber, Thread.currentThread().getId());
   }
}
    

該類對處理傳入請求的行為進行建模並顯示其編號。

接下來,在main方法中,我們為 1 個線程創建一個ExecutorService,我們將使用它來順序處理傳入的請求。由於任務條件規定“30秒以內”,我們加上30秒等待,然後強行停止ExecutorService


public static void main(String[] args) throws InterruptedException {
   ExecutorService executorService = Executors.newSingleThreadExecutor();

   for (int i = 0; i < 1_000; i++) {
       executorService.execute(new Task(i));
   }
   executorService.awaitTermination(30, TimeUnit.SECONDS);
   executorService.shutdownNow();
}
    

啟動程序後,控制台顯示有關請求處理的消息:

線程 ID = 16 上的已處理請求 #0
線程 ID = 16 上的已處理請求 #1 線程
ID = 16 上的已處理請求 #2
...線程 ID =
16 上的已處理請求 #29

處理請求 30 秒後,executorService調用shutdownNow()方法,該方法停止當前任務(正在執行的任務)並取消所有掛起的任務。之後,程序成功結束。

但一切並不總是那麼完美,因為我們的程序很容易出現這樣的情況,即我們池中唯一的線程拾取的任務之一工作不正確,甚至終止我們的線程。我們可以模擬這種情況來弄清楚executorService在這種情況下如何與單個線程一起工作。

為此,在執行其中一項任務時,我們使用不安全且過時的Thread.currentThread().stop()方法終止我們的線程。我們這樣做是為了模擬其中一個任務終止線程的情況。

我們將更改Task類中的運行方法:


@Override
public void run() {
   try {
       Thread.sleep(1000);
   } catch (InterruptedException ignored) {
   }

   if (taskNumber == 5) {
       Thread.currentThread().stop();
   }

   System.out.printf("Processed request #%d on thread id=%d\\n", taskNumber, Thread.currentThread().getId());
}
    

我們將中斷任務#5。

讓我們看看線程在任務 #5 結束時被中斷的輸出是什麼樣子的:

線程 id=16 上的已處理請求 #0
線程 id=16 上的已
處理請求 #1 線程 id=16 上的已
處理請求 #2 線程 id=16 上的已處理請求 #3 線程 id=16 上的
已處理請求 #4 已
處理請求 #6線程 id=17
線程 id=17 上的已處理請求 #7

線程 id=17 上已處理的請求 #29

我們看到線程在任務 5 結束時被中斷後,任務開始在標識符為 17 的線程中執行,儘管它們之前是在標識符為 16 的線程上執行的。並且因為我們的池有一個單線程,這只能說明一件事:executorService用一個新的線程替換了停止的線程並繼續執行任務。

因此,當我們想要順序處理任務並且一次只處理一個任務時,我們應該使用帶有單線程池的 newSingleThreadExecutor,並且我們想要繼續處理隊列中的任務而不管前一個任務是否完成(例如,一個我們的任務殺死了線程)。

線程工廠

說到創建和重新創建線程,我們不能不提線程工廠.

A線程工廠是一個按需創建新線程的對象。

我們可以創建自己的線程創建工廠並將其實例傳遞給Executors.newSingleThreadExecutor(ThreadFactory threadFactory)方法。


ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "MyThread");
            }
        });
                    
我們覆蓋創建新線程的方法,將線程名稱傳遞給構造函數。

ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r, "MyThread");
                thread.setPriority(Thread.MAX_PRIORITY);
                return thread;
            }
        });
                    
我們更改了創建線程的名稱和優先級。

所以我們看到我們有 2 個重載的Executors.newSingleThreadExecutor方法。一個沒有參數,第二個帶有ThreadFactory參數。

使用ThreadFactory,您可以根據需要配置創建的線程,例如,通過設置優先級、使用線程子類、向線程添加 UncaughtExceptionHandler