ExecutorService - スレッドが終了するのを待っている

ExecutorService –スレッドの終了を待機

1. 概要

ExecutorServiceフレームワークを使用すると、複数のスレッドでタスクを簡単に処理できます。 スレッドが実行を終了するのを待ついくつかのシナリオを例示します。

また、ExecutorServiceを正常にシャットダウンし、すでに実行中のスレッドが実行を終了するのを待つ方法も示します。

2. Executor’sシャットダウン後

Executor,を使用する場合、shutdown()またはshutdownNow()メソッドを呼び出すことでシャットダウンできます。 Although, it won’t wait until all threads stop executing.

既存のスレッドが実行を完了するのを待つことは、awaitTermination()メソッドを使用することで実現できます。

これにより、すべてのタスクの実行が完了するか、指定されたタイムアウトに達するまでスレッドがブロックされます。

public void awaitTerminationAfterShutdown(ExecutorService threadPool) {
    threadPool.shutdown();
    try {
        if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) {
            threadPool.shutdownNow();
        }
    } catch (InterruptedException ex) {
        threadPool.shutdownNow();
        Thread.currentThread().interrupt();
    }
}

3. CountDownLatchの使用

次に、この問題を解決するための別のアプローチを見てみましょう。CountDownLatchを使用してタスクの完了を通知します。

await()メソッドを呼び出したすべてのスレッドに通知される前に、デクリメントできる回数を表す値で初期化できます。

たとえば、現在のスレッドが別のNスレッドの実行を終了するのを待つ必要がある場合、Nを使用してラッチを初期化できます。

ExecutorService WORKER_THREAD_POOL
  = Executors.newFixedThreadPool(10);
CountDownLatch latch = new CountDownLatch(2);
for (int i = 0; i < 2; i++) {
    WORKER_THREAD_POOL.submit(() -> {
        try {
            // ...
            latch.countDown();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    });
}

// wait for the latch to be decremented by the two remaining threads
latch.await();

4. invokeAll()の使用

スレッドを実行するために使用できる最初のアプローチは、invokeAll()メソッドです。 The method returns a list of Future objects after all tasks finish or the timeout expires

また、返されるFutureオブジェクトの順序は、提供されたCallableオブジェクトのリストと同じであることに注意する必要があります。

ExecutorService WORKER_THREAD_POOL = Executors.newFixedThreadPool(10);

List> callables = Arrays.asList(
  new DelayedCallable("fast thread", 100),
  new DelayedCallable("slow thread", 3000));

long startProcessingTime = System.currentTimeMillis();
List> futures = WORKER_THREAD_POOL.invokeAll(callables);

awaitTerminationAfterShutdown(WORKER_THREAD_POOL);

long totalProcessingTime = System.currentTimeMillis() - startProcessingTime;

assertTrue(totalProcessingTime >= 3000);

String firstThreadResponse = futures.get(0).get();

assertTrue("fast thread".equals(firstThreadResponse));

String secondThreadResponse = futures.get(1).get();
assertTrue("slow thread".equals(secondThreadResponse));

5. ExecutorCompletionServiceの使用

複数のスレッドを実行する別のアプローチは、ExecutorCompletionService.を使用することです。提供されたExecutorServiceを使用してタスクを実行します。

invokeAll()との違いの1つは、実行されたタスクを表すFutures,が返される順序です。 ExecutorCompletionService uses a queue to store the results in the order they are finishedは、invokeAll()が、指定されたタスクリストのイテレータによって生成されたものと同じ順序のリストを返します。

CompletionService service
  = new ExecutorCompletionService<>(WORKER_THREAD_POOL);

List> callables = Arrays.asList(
  new DelayedCallable("fast thread", 100),
  new DelayedCallable("slow thread", 3000));

for (Callable callable : callables) {
    service.submit(callable);
}

結果には、take()メソッドを使用してアクセスできます。

long startProcessingTime = System.currentTimeMillis();

Future future = service.take();
String firstThreadResponse = future.get();
long totalProcessingTime
  = System.currentTimeMillis() - startProcessingTime;

assertTrue("First response should be from the fast thread",
  "fast thread".equals(firstThreadResponse));
assertTrue(totalProcessingTime >= 100
  && totalProcessingTime < 1000);
LOG.debug("Thread finished after: " + totalProcessingTime
  + " milliseconds");

future = service.take();
String secondThreadResponse = future.get();
totalProcessingTime
  = System.currentTimeMillis() - startProcessingTime;

assertTrue(
  "Last response should be from the slow thread",
  "slow thread".equals(secondThreadResponse));
assertTrue(
  totalProcessingTime >= 3000
  && totalProcessingTime < 4000);
LOG.debug("Thread finished after: " + totalProcessingTime
  + " milliseconds");

awaitTerminationAfterShutdown(WORKER_THREAD_POOL);

6. 結論

ユースケースに応じて、スレッドの実行が完了するまで待機するさまざまなオプションがあります。

CountDownLatchは、他のスレッドによって実行された一連の操作が終了したことを1つ以上のスレッドに通知するメカニズムが必要な場合に役立ちます。

ExecutorCompletionServiceは、タスクの結果にできるだけ早くアクセスする必要がある場合や、実行中のすべてのタスクが終了するのを待つ必要がある場合に役立ちます。

記事のソースコードはover on GitHubで入手できます。