Java 当前的并发实现是非结构化的,这可能会使处理多个任务的错误处理和取消变得困难。当异步启动多个任务时,如果第一个任务返回错误,我们目前无法取消剩余的任务。
通过JEP 中的示例代码来说明这一点:
Response handle() throws ExecutionException, InterruptedException {
Future < String > user = executor.submit(() - > findUser());
Future < Integer > order = executor.submit(() - > fetchOrder());
String theUser = user.get(); // Join findUser int theOrder = order.get(); // Join fetchOrder return new Response(theUser, theOrder);}
当user.get()
调用出错时,没有办法取消第二个任务,从而防止获取一个不会被使用的结果。
不过,如果将此代码重写为只使用一个线程,情况将变得简单得多:
Response handle() throws IOException {
String theUser = findUser();
int theOrder = fetchOrder();
return new Response(theUser, theOrder);
}
在Java 中,如果任务及其子任务之间的父子关系在语法上得到了表达,那么多线程编程将更加容易、可靠和可观察 - 就像单线程代码一样。语法结构将定义子任务的生命周期,并启用一个运行时表示线程间层次结构,从而实现错误传播和取消以及并发程序的有意义观察。
这就是所谓的“结构化并发”。现在使用新的 StructuredTaskScope API
对代码示例进行重写:
Response handle() throws ExecutionException, InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future < String > user = scope.fork(() - > findUser());
Future < Integer > order = scope.fork(() - > fetchOrder());
scope.join(); // Join both forks scope.throwIfFailed(); // ... and propagate errors // Here, both forks have succeeded, so compose their results return new Response(user.resultNow(), order.resultNow()); }}
在结构化并发中,子任务代表任务工作。任务等待子任务的结果并监视它们的失败。StructuredTaskScope
类允许开发人员将任务结构化为并发子任务家族,并将它们作为一个单元协调。子任务通过单独分叉它们在自己的线程中执行,然后将它们作为一个单元加入并可能作为一个单元取消。父任务处理子任务的成功结果或异常。
与原始示例相比,这里涉及的线程的生命周期理解起来很容易。在所有情况下,它们的生命周期都限于词法作用域,即 try-with-resources
语句的主体。此外,使用 StructuredTaskScope
确保了许多有价值的属性:
- 短路错误处理: 如果子任务失败,则另一个子任务将被取消(如果它尚未完成)。这由 ShutdownOnFailure 实现的取消策略管理;其他策略,如 ShutdownOnSuccess 也是可用的。
- 取消传播: 如果在调用 join() 之前或期间中断运行 handle() 的线程,则当该线程退出作用域时,两个分叉将自动取消。
- 清晰可见: 上面的代码具有明确的结构:设置子任务,等待它们完成或被取消,然后决定是成功(并处理子任务的结果,这些结果已经完成)还是失败(子任务已经完成,因此没有更多的清理工作)。
顺便说一下,结构化并发正好与虚拟线程同时出现在 Java 中,这绝非偶然。现代 Java 程序可能会使用大量的线程,并且需要正确而稳健地协调它们。结构化并发正好可以提供这种功能,同时还可以使观察工具按照开发人员的理解显示线程。
与 Java 19 有何不同
情况与Java 19 中的情况大致相同(请参见 JEP 428)。唯一的变化是更新了 StructuredTaskScope
,以使其支持在任务作用域中创建的线程继承作用域值。这简化了在线程之间共享不可变数据。再次注意,JEP 处于孵化器阶段,因此需要将--enable-preview --add-modules jdk.incubator.concurrent
添加到命令行中,以便能够使用该功能。
Panama 项目
Java 20 包含了两个源自 Panama 项目的功能:
- 外部函数与内存 API
- Vector API
Panama 项目旨在改善 JVM 与外部(非 Java)库之间的连接。