1. 概述

在本教程中,我们将讨论孵化器功能结构化并发 (JEP 428),它为 Java 19 提供了结构化并发功能。我们将指导你使用新的 API 来管理多线程代码。

2. 理念

通过采用并发编程风格来降低线程泄漏和取消延迟的可能性,从而增强多线程代码的可维护性、可靠性和可观察性,这是与取消和关闭相关的常见风险。为了更好地理解非结构化并发的问题,让我们看一个例子:

Future shelter;

Future> 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 theDogs = dogs.get(); // Join the dogs

Response response = new Response(theShelter, theDogs);

} catch (ExecutionException | InterruptedException e) {

throw new RuntimeException(e);

}Copy

当getShelter()运行时,代码不会注意到getDogs()是否可能失败,并且将继续不必要的阻塞shelter.get()调用的原因。因此,只有在getShelter()完成和getDogs()returns之后,dogs.get()才会抛出异常,我们的代码才会失败:

但这不是唯一的问题。当执行代码的线程被中断时,它不会将中断传播到我们的子任务。此外,如果第一个执行的子任务庇护所抛出异常,它不会被委派给狗的子任务,它会继续运行,浪费资源。

结构化并发试图解决这些问题,我们将在下一章中看到。

3. 示例 

对于结构化并发示例,我们将使用以下记录:

record Shelter(String name) { }

record Dog(String name) { }

record Response(Shelter shelter, List dogs) { }Copy

我们还将提供两种方法。一个获得庇护所:

private Shelter getShelter() {

return new Shelter("Shelter");

}Copy

另一种是检索Dog元素列表:

private List getDogs() {

return List.of(new Dog("Buddy"), new Dog("Simba"));

}Copy

由于结构化并发是孵化器功能,因此我们必须使用以下参数运行应用程序:

 

--enable-preview --add-modules jdk.incubator.foreignCopy

否则,我们可以添加一个模块信息.java并将包标记为必需。

让我们看一个例子:

try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {

Future shelter = scope.fork(this::getShelter);

Future> dogs = scope.fork(this::getDogs);

scope.join();

Response response = new Response(shelter.resultNow(), dogs.resultNow());

// ...

}Copy

由于StructuredTaskScope实现了AutoCloseable接口,因此我们可以在 try-with-resources 语句中使用它。StructuredTaskScope为我们提供了两个子类,它们有不同的用途。在本教程中,我们将使用ShutdownOnFailure(),它会在出现问题时关闭子任务。

还有一个ShutdownOnSuccess() 构造函数,它的作用恰恰相反。如果成功,它会关闭子任务。这种短路模式有助于我们避免不必要的工作。

StructuredTaskScope的使用与同步代码的结构非常相似。创建范围的线程是所有者。作用域允许我们在作用域中分叉其他子任务。此代码以异步方式调用。在join() 方法的帮助下,我们可以阻止所有任务,直到它们交付结果。

每个任务都可以借助作用域的shutdown() 方法终止其他任务。throwIfFailed() 方法提供了另一种可能性:

scope.throwIfFailed(e -> new RuntimeException("ERROR_MESSAGE"));

Copy

它允许我们在任何分叉失败时传播任何异常。此外,我们还可以设置一个截止日期加入直到:

scope.joinUntil(Instant.now().plusSeconds(1));

Copy

如果任务尚未完成,这将在时间到期后引发异常。

参考文章

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。