1. 概述
在这个教程中,我们将探讨Java 19引入的孵化特性——结构化并发(JEP 428),它为多线程代码提供了更高效的并发能力管理。我们将指导您如何使用新API来管理多线程代码,提升其可维护性、可靠性和可观测性。
2. 主题
通过采用一种减少取消和关闭时常见问题(如线程泄露和取消延迟)的并发编程风格,增强多线程代码的健壮性。让我们先来看看一个未结构化的并发问题示例:
Future<Shelter> shelter;
Future<List<Dog>> dogs;
try (ExecutorService executorService = Executors.newFixedThreadPool(3)) {
shelter = executorService.submit(this::getShelter);
dogs = executorService.submit(this::getDogs);
Shelter theShelter = shelter.get(); // Join the shelter
List<Dog> theDogs = dogs.get(); // Join the dogs
Response response = new Response(theShelter, theDogs);
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}
当getShelter()
运行时,如果getDogs()
可能失败,代码可能不会察觉到,并会由于阻塞的shelter.get()
调用而继续执行,导致不必要的问题。因此,只有在getShelter()
完成并返回后,dogs.get()
才会抛出异常,导致代码失败:
但这并不是唯一的问题。当执行代码的线程被中断时,它不会将中断传递给子任务。此外,如果第一个执行的子任务shelter抛出异常,它不会传递给狗狗的子任务,而是继续运行,浪费资源。
结构化并发试图解决这些问题,我们将在下一章中详细介绍。
3. 示例
对于结构化并发的示例,我们将使用以下记录:
record Shelter(String name) { }
record Dog(String name) { }
record Response(Shelter shelter, List<Dog> dogs) { }
我们将提供两个方法:获取一个Shelter
:
private Shelter getShelter() {
return new Shelter("Shelter");
}
另一个是获取一个Dog
列表:
private List<Dog> getDogs() {
return List.of(new Dog("Buddy"), new Dog("Simba"));
}
由于结构化并发是孵化特性,我们需要使用以下参数运行应用程序:
--enable-preview --add-modules jdk.incubator.foreign
否则,我们可以添加一个module-info.java
文件,并标记包为必需的。
让我们看一个例子:
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<Shelter> shelter = scope.fork(this::getShelter);
Future<List<Dog>> dogs = scope.fork(this::getDogs);
scope.join();
Response response = new Response(shelter.resultNow(), dogs.resultNow());
// ...
}
由于StructuredTaskScope
实现了AutoCloseable
接口,我们可以在try-with-resources语句中使用它。StructuredTaskScope
提供了两个子类,各有不同的用途。在这篇教程中,我们将使用ShutdownOnFailure()
,它在遇到错误时关闭子任务。
还有ShutdownOnSuccess()
构造函数,它的作用相反,在成功时关闭子任务。这种短路模式有助于避免不必要的工作。
使用StructuredTaskScope
的方式类似于同步代码的结构。创建scope
的线程是所有者,scope
允许我们在其中fork
更多的子任务。这些代码异步执行,通过join()
方法可以阻塞直到所有任务完成。
每个任务都可以使用scope
的shutdown()
方法终止其他任务。throwIfFailed()
方法提供了另一种选择:
scope.throwIfFailed(e -> new RuntimeException("ERROR_MESSAGE"));
它允许在任何子任务失败时传播异常。此外,我们还可以设置截止日期,使用joinUntil
:
scope.joinUntil(Instant.now().plusSeconds(1));
如果任务在截止日期前还未完成,这将会抛出异常。
4. 总结
本文讨论了无结构并发的缺点以及结构化并发如何解决这些问题。我们学习了如何处理错误和实现截止日期。同时,我们也看到新的构造方式使编写可维护、易读且可靠的多线程同步代码变得更加容易。
一如既往,这些示例也可以在GitHub上找到。