簡単なプログラムを考えてみましょう。


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 を「シャットダウン」(または停止) する必要があります。これは 2 つの方法で行うことができます。

  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[ラップされたタスク = Test$$Lambda$16/0x0000000800b95040@65b3120a]]
5

プロセスは終了コード 0 で終了しました

出力は実行ごとに異なります。出力には 2 種類の行があります。

  • 数字は、ExecutorServiceが対応するタスクを処理できたことを意味し、タスクの作成に使用したリストの数字が表示されます。

  • FutureTaskオブジェクトのtoString()メソッドを呼び出した結果。これらのオブジェクトは、 ExecutorServiceに送信されたものの処理されなかったタスクです。

出力には、別の興味深いニュアンスがあります。理想的な世界では、最初に表示されているすべての数値が表示され、次にFutureTaskオブジェクトが表示されます。ただし、同期の問題により、出力の行が乱雑になります。

その他の方法

ExecutorService には、停止に関連するメソッドがさらにいくつかあります。

  1. boolean awaitTermination(long timeout, TimeUnit単位) — このメソッドは、それを呼び出すスレッドをブロックします。ブロックは、次の 3 つのイベントのいずれかが発生するとすぐに終了します。

    • 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()shutdown()またはshutdownNow()メソッドがExecutorServiceで呼び出された場合は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()shutdown()またはshutdownNow()メソッドがExecutorServiceで呼び出され、すべてのタスクが完了した場合は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 で終了しました