admin管理员组

文章数量:1441208

ExecutorService之等待线程完成

1. 概述

ExecutorService框架使在多个线程中处理任务变得容易。我们将举例说明一些等待线程完成执行的场景。

此外,我们还将展示如何优雅地关闭ExecutorService并等待已经运行的线程完成其执行。

2.Executor关闭

当使用Executor时,我们可以通过调用shutdown()或shutdownNow()方法来关闭它。不过,它不会等到所有线程停止执行。等待现有线程完成它们的执行可以通过使用waitterminate()方法实现。这将阻塞线程,直到所有任务完成执行或到达指定的超时:

代码语言:javascript代码运行次数:0运行复制
public void awaitTerminationAfterShutdown(ExecutorService threadPool) {
    threadPool.shutdown();
    try {
        if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) {
            threadPool.shutdownNow();
        }
    } catch (InterruptedException ex) {
        threadPool.shutdownNow();
        Thread.currentThread().interrupt();
    }
}Copy

3. 使用CountDownLatch(倒计时闩锁)

接下来,让我们看看解决这个问题的另一种方法 - 使用CountDownLatch来表示任务完成。

我们可以用一个值来初始化它,该值表示在所有调用await() 方法的线程收到通知之前它可以递减的次数。

例如,如果我们需要当前线程等待另外 N 个线程完成它们的执行,我们可以使用N 初始化闩锁:

代码语言:javascript代码运行次数:0运行复制
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();Copy

4. 使用invokeAll()

我们可以用来运行线程的第一种方法是invokeAll() 方法。该方法在所有任务完成或超时到期后返回Future对象的列表。

此外,我们必须注意,返回的Future对象的顺序与提供的Callable对象的列表相同:

代码语言:javascript代码运行次数:0运行复制
ExecutorService WORKER_THREAD_POOL = Executors.newFixedThreadPool(10);

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

long startProcessingTime = System.currentTimeMillis();
List<Future<String>> 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));Copy

5. 使用ExecutorCompletionService

运行多个线程的另一种方法是使用ExecutorCompletionService。它使用提供的执行器服务来执行任务。

与 invokeAll() 的一个区别是返回代表已执行任务的期货的顺序。ExecutorCompletionService使用队列按完成顺序存储结果,而invokeAll() 返回一个列表,其顺序与迭代器为给定任务列表生成的顺序相同:

代码语言:javascript代码运行次数:0运行复制
CompletionService<String> service
  = new ExecutorCompletionService<>(WORKER_THREAD_POOL);

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

for (Callable<String> callable : callables) {
    service.submit(callable);
}
Copy

可以使用take() 方法访问结果:

代码语言:javascript代码运行次数:0运行复制
long startProcessingTime = System.currentTimeMillis();

Future<String> 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);Copy

6. 结论

根据用例,我们有各种选项来等待线程完成其执行。

当我们需要一种机制来通知一个或多个线程其他线程执行的一组操作已完成时,CountDownLatch很有用。

当我们需要尽快访问任务结果时,ExecutorCompletionService很有用,当我们想要等待所有正在运行的任务完成时,其他方法很有用。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。 原始发表:2023-02-16,如有侵权请联系 cloudcommunity@tencent 删除javaexecutorservice对象教程线程

本文标签: ExecutorService之等待线程完成