Java中如何让应用线程等待其他线程完成,使用ExecutorService方法和CountDownLatch类

654 阅读3分钟

Java并发性允许在不同的线程中运行一个任务的多个子任务。有时,有必要等待所有的线程都执行完毕。在本教程中,我们将学习一些方法,使当前线程等待其他线程完成。

1.使用ExecutorServiceFuture.get()

Java*[ExecutorService](或 [ThreadPoolExecutor] 帮助异步执行RunnableCallable任务。它的submit()方法返回一个Future*对象,我们可以用它来取消执行和/或等待完成。

在下面的例子中,我们有一个演示的Runnable任务。每个任务在0到1秒之间的随机时间内完成。

public class DemoRunnable implements Runnable {

  private Integer jobNum;

  public DemoRunnable(Integer index) {
    this.jobNum = index;
  }

  @SneakyThrows
  @Override
  public void run() {
    Thread.sleep(new Random(0).nextLong(1000));
    System.out.println("DemoRunnable completed for index : " + jobNum);
  }
}

我们正在提交10个任务给执行者服务。然后,我们在向执行器提交任务后收到的每个Future对象上调用*Future.get()*方法。*Future.get()*在必要时等待任务完成,然后检索其结果。

ExecutorService executor = Executors.newFixedThreadPool(5);

List<Future<?>> futures = new ArrayList<>();

for (int i = 1; i <= 10; i++) {
  Future<?> f = executor.submit(new DemoRunnable(i));
  futures.add(f);
}

System.out.println("###### All tasks are submitted.");

for (Future<?> f : futures) {
  f.get();
}

System.out.println("###### All tasks are completed.");
###### All tasks are submitted.
DemoRunnable completed for index : 3
DemoRunnable completed for index : 4
DemoRunnable completed for index : 1
DemoRunnable completed for index : 5
DemoRunnable completed for index : 2
DemoRunnable completed for index : 6
DemoRunnable completed for index : 10
DemoRunnable completed for index : 7
DemoRunnable completed for index : 9
DemoRunnable completed for index : 8
###### All tasks are completed.

注意,在以下情况下,等待可能会提前终止。

  • 该任务被取消了
  • 任务的执行产生了一个异常
  • 有一个InterruptedException,即当前线程在等待时被打断了。

在这种情况下,我们应该实现自己的逻辑来[处理这个异常]。

2.使用ExecutorService shutdown()awaitTermination()

[awaitTermination()]方法在执行器服务上发出shutdown()请求后,会阻止所有任务完成执行。与Future.get()类似,如果发生超时,或者当前线程被中断,它可以提前解除阻塞。

shutdown()方法关闭了执行器,因此不能再提交新的任务,但之前提交的任务继续执行。

下面这个方法的完整逻辑是等待所有任务在1分钟内完成。之后,将使用shutdownNow()方法强行关闭执行器服务。

void shutdownAndAwaitTermination(ExecutorService executorService) {
    executorService.shutdown();
    try {
        if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
            executorService.shutdownNow();
        } 
    } catch (InterruptedException ie) {
        executorService.shutdownNow();
        Thread.currentThread().interrupt();
    }
}

我们可以使用这个方法,如下:

ExecutorService executor = Executors.newFixedThreadPool(5);

for (int i = 1; i <= 10; i++) {
  executor.submit(new DemoRunnable(i));
}

System.out.println("###### All tasks are submitted.");

shutdownAndAwaitTermination(executor);

System.out.println("###### All tasks are completed.");

3.使用ExecutorService invokeAll()

这种方法可以看作是前两种方法的结合。它接受任务作为一个集合,并返回一个Future对象的列表,以便在必要时检索输出。同时,它使用shutdown和awaitits逻辑来等待任务的完成。

在下面的例子中,我们使用的是DemoCallable类,它与DemoRunnable非常相似,只是它返回一个整数值

ExecutorService executor = Executors.newFixedThreadPool(10);

List<DemoCallable> tasks = Arrays.asList(
    new DemoCallable(1), new DemoCallable(2),
    new DemoCallable(3), new DemoCallable(4),
    new DemoCallable(5), new DemoCallable(6),
    new DemoCallable(7), new DemoCallable(8),
    new DemoCallable(9), new DemoCallable(10));

System.out.println("###### Submitting all tasks.");

List<Future<Integer>> listOfFutures = executor.invokeAll(tasks);

shutdownAndAwaitTermination(executor);

System.out.println("###### All tasks are completed.");

请注意,listOfFutures ,以我们提交任务给执行者服务的相同顺序存储任务输出。

for (Future f : listOfFutures) {
  System.out.print(f.get() + " "); //Prints 1 2 3 4 5 6 7 8 9 10 
}

4.使用CountDownLatch

CountDownLatch 类使一个Java线程能够等待,直到一个线程集合(latch正在等待)完成其任务。

CountDownLatch的工作原理是,有一个用线程数量初始化的计数器,每当一个线程完成其执行时,该计数器就被递减。当计数达到0时,意味着所有的线程都完成了它们的执行,在锁存器上等待的主线程就恢复了执行。

在下面的例子中,主线程正在等待3个给定的服务完成,然后报告最终的系统状态。我们可以在CountDownLatch例子中阅读整个例子。

CountDownLatch latch = new CountDownLatch(3);

List<BaseHealthChecker> services = new ArrayList<>();
services.add(new NetworkHealthChecker(latch));
services.add(new CacheHealthChecker(latch));
services.add(new DatabaseHealthChecker(latch));

Executor executor = Executors.newFixedThreadPool(services.size());
     
for(final BaseHealthChecker s : services) {
  executor.execute(s);
}
 
//Now wait till all health checks are complete
latch.await();

5.总结

在本教程中,我们学习了如何让一个应用线程等待其他线程完成。我们学会了使用ExecutorService方法和CountDownLatch类。