考慮一個簡單的程序:


public static void main(String[] args) throws Exception {
	// Create an ExecutorService with a fixed number of threads: three
	ExecutorService service = Executors.newFixedThreadPool(3);
 
	// Pass a simple Runnable task to the ExecutorService
	service.submit(() -> System.out.println("done"));
}

運行該程序會產生我們期望的控制台輸出:

完畢

但這並不是我們通常在 IntelliJ IDEA 中看到的輸出:

進程結束,退出代碼為 0

我們通常會在程序結束時看到。

為什麼會這樣?

newFixedThreadPool()方法的描述告訴我們,使用ExecutorService創建的線程將繼續存在,直到它們被顯式停止。這意味著因為我們將任務傳遞給 ExecutorService 所以創建了一個線程來執行它,並且即使在任務完成後該線程仍然存在。

在 ExecutorService 處停止

因此,我們需要“關閉”(或停止)ExecutorService。我們可以通過兩種方式做到這一點:

  1. void shutdown() — 調用此方法後,ExecutorService停止接受新作業。先前提交給ExecutorService 的所有任務將繼續運行。

    
    public static void main(String[] args) throws Exception {
    ExecutorService service = Executors.newFixedThreadPool(3);
        	service.submit(() -> System.out.println("task 1"));
        	service.submit(() -> System.out.println("task 2"));
        	service.shutdown();
        	// A RejectedExecutionException will occur here
        	service.submit(() -> System.out.println("task 3"));
    }
    
  2. List<Runnable> shutdownNow() — 此方法嘗試停止當前活動的作業。仍在等待輪到他們的任務被丟棄並作為Runnables列表返回。

    
    public static void main(String[] args) throws Exception {
        ExecutorService service = Executors.newFixedThreadPool(5);
        List.of(1, 2, 3, 4, 5, 6, 7, 8).forEach(i -> service.submit(() -> System.out.println(i)));
        List<Runnable> runnables = service.shutdownNow();
        runnables.forEach(System.out::println);
    }
    

輸出:

1
2
4
3
java.util.concurrent.FutureTask@1e80bfe8[未完成,任務 = java.util.concurrent.Executors$RunnableAdapter@4edde6e5[包裝任務 = Test$$Lambda$16/0x0000000800b95040@70177ecd]]
java.util.concurrent .FutureTask@cc34f4d[未完成,任務 = java.util.concurrent.Executors$RunnableAdapter@66a29884[包裝任務 = Test$$Lambda$16/0x0000000800b95040@4769b07b]]
java.util.concurrent.FutureTask@6f539caf[未完成,任務= java.util.concurrent.Executors$RunnableAdapter@17a7cec2[Wrapped task = Test$$Lambda$16/0x0000000800b95040@65b3120a]]
5

進程結束,退出代碼為 0

輸出將因運行而異。輸出中有兩種行:

  • 數字表示 ExecutorService設法處理了相應的任務,顯示了我們用來創建任務的列表中的數字。

  • 在FutureTask對像上調用toString()方法的結果。這些對像是提交給ExecutorService但未處理的任務。

輸出還有另一個有趣的細微差別。在理想情況下,我們首先會看到所有顯示的數字,然後是FutureTask對象。但是同步問題使輸出中的行混亂。

其他方法

ExecutorService還有幾個與停止它相關的方法:

  1. boolean awaitTermination(long timeout, TimeUnit unit) — 此方法阻塞調用它的線程。一旦發生以下三個事件中的任何一個,塊就會結束:

    • 調用shutdown()方法後,所有活動作業和所有計劃任務都已執行;
    • 方法參數確定的超時已經過去;
    • 調用awaitTermination()方法的線程被終止。

    如果ExecutorService在超時結束前停止,則該方法返回true ,如果超時已經結束,則返回 false 。

    
    public static void main(String[] args) throws Exception {
    	ExecutorService service = Executors.newFixedThreadPool(2);
    	service.submit(() -> System.out.println("task 1"));
    	service.submit(() -> System.out.println("task 2"));
    	service.submit(() -> System.out.println("task 3"));
    	service.shutdown();
    	System.out.println(service.awaitTermination(1, TimeUnit.MICROSECONDS));
    }
    
  2. boolean isShutdown() —如果在ExecutorService上調用了shutdown()shutdownNow()方法,則返回true

    
    public static void main(String[] args) throws Exception {
    	ExecutorService service = Executors.newFixedThreadPool(2);
    	service.submit(() -> System.out.println("task 1"));
    	service.submit(() -> System.out.println("task 2"));
    	service.submit(() -> System.out.println("task 3"));
    	System.out.println(service.isShutdown());
    	service.shutdown();
    	System.out.println(service.isShutdown());
    }
    
  3. boolean isTerminated() —如果已在ExecutorService上調用shutdown()shutdownNow()方法並且所有任務都已完成,則返回true 。

    
    public static void main(String[] args) throws Exception {
        ExecutorService service = Executors.newFixedThreadPool(5);
        List.of(1, 2, 3, 4, 5, 6, 7, 8).forEach(i -> service.submit(() -> System.out.println(i)));
        service.shutdownNow();
        System.out.println(service.isTerminated());
    }
    

使用這些方法的示例代碼:


public static void main(String[] args) throws Exception {
   ExecutorService service = Executors.newFixedThreadPool(16);
   Callable<String> task = () -> {
       Thread.sleep(1);
       return "Done";
   };
 
   // Add 10,000 tasks to the queue
   List<Future<String>> futures = IntStream.range(0, 10_000)
           .mapToObj(i -> service.submit(task))
           .collect(Collectors.toList());
   System.out.printf("%d tasks were submitted for execution.%n", futures.size());
 
   // Attempt to shut down
   service.shutdown();
   // Wait 100 milliseconds to finish the work
   if (service.awaitTermination(100, TimeUnit.MILLISECONDS)) {
       System.out.println("All tasks completed!");
   } else {
       // Stop forcibly
       List<Runnable> notExecuted = service.shutdownNow();
       System.out.printf("%d tasks were not started.%n", notExecuted.size());
   }
 
   System.out.printf("Total tasks completed: %d.%n", futures.stream().filter(Future::isDone).count());
}

輸出(每次運行都不同):

提交了 10,000 個任務以供執行。
9170 任務未啟動。
完成的任務總數:830 項任務。

進程結束,退出代碼為 0