Java并发性允许在不同的线程中运行一个任务的多个子任务。有时,有必要等待所有的线程都执行完毕。在本教程中,我们将学习一些方法,使当前线程等待其他线程完成。
1.使用ExecutorService和Future.get()
Java*[ExecutorService](或 [ThreadPoolExecutor] 帮助异步执行Runnable或Callable任务。它的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类。