JDK 19 添加了Structured Concurrency
相关的功能,与多线程相关,StructuredTaskScope
正是其中的一个类。在jdk19/jdk20中,StructuredTaskScope
位于jdk.incubator.concurrent
包下,incubator是孵化器的意思。在jdk21中位于java.util.concurrent
下,从孵化器正式纳入java.util
中了。
StructuredTaskScope
从jdk19到jdk21,还是有些许变化的,比如fork方法的返回值等等,本文使用的是jdk21编程。
【参考】
- 官方文档:StructuredTaskScope
- www.youtube.com/watch?v=Wfy...
- jfokus.se/jfokus23-pr...
- belief-driven-design.com/looking-at-...
1. 使用jdk21的准备
jdk下载:www.oracle.com/cn/java/tec...
需要下载2023.3版本的IntelliJ IDEA:
2. 开启--enable-preview
安装好后,想要使用StructuredTaskScope
会报错:
less
java: java.util.concurrent.StructuredTaskScope 是预览 API,默认情况下处于禁用状态。
(请使用 --enable-preview 以启用预览 API)
解决办法,可以在pom.xml中加入:
xml
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>21</source>
<target>21</target>
<compilerArgs>--enable-preview</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
3. Structured Concurrency
传统的jdk中的多线程提供的方式都是Unstructured,比如Thread创建,需要手动的管理,虽然有一些框架,如适合异步的CompletableFuture
或者ExcutorService
(以线程池的方式管理线程)。但总体上,这些类提供的API都是非常灵活的,即怎么写都可能能达到想要的效果,但灵活的编程也意味着不可控。
所以在jdk19提供了Structured Concurency的概念,它主要提供了一种更为容易的方式来处理任务以及子任务。
4. 使用StructuredTaskScope
StructuredTaskScope
是一个scope的概念,在这个scope中,我们可以创建自己的subtask,在最后join(),拿到我们想要的结果。
我们可以创建StructuredTaskScope
本身的instance,或它的子类:
StructuredTaskScope<T>()
,也可以创建自己的类再继承它。StructuredTaskScope.ShutdownOnFailure()
,子类之一。如果遇到失败的subtask,那么会关闭整个scope。如果我们需要得到所有子任务的结果(相当于invokeAll()),那么如果子任务有失败,那么其它的子任务会中止行动。StructuredTaskScope.ShutdownOnSuccess()
,子类之一。捕捉第一个成功的子任务,如果有,就会关闭整个scope,这时候可能会中止其它的子任务,唤起scope的owner,相当于invokeAny()。
在上述参考链接中,第二个链接《Walkthrough of the Java StructuredTaskScope Code》视频中有个比喻:
-
ShutdownOnFailure
的过程相当于子任务是一群整齐划一的士兵,所以需要一起行动,如果遇到撤退事件,也是所有的士兵一起撤退: -
ShutdownOnSuccess
的过程相当于子任务之间的有奖竞猜,最先按下抢答按钮,谁就有回答的权利,这时候并不在乎其它选手的答案了:
4.1 ShutdownOnFailure
示例
- 首先,
ShutdownOnFailure
用完后需要关闭,它实现了AutoCloseable
接口,可以用try-with-resources
语句来写。 scope.join()
:这个方法会等所有的subtask结束后进行一个"汇总"。- 如果在
join()
方法后加上throwIfFailed()
,那么在遇到单个subtask exception时,会抛出异常ExecutionException
。
csharp
public String getResources() throws InterruptedException, ExecutionException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Supplier<String> orderSupplier = scope.fork(() -> getOrder());
Supplier<String> userSupplier = scope.fork(() -> getUser());
scope.join().throwIfFailed();
String resources = orderSupplier.get() + ", " + userSupplier.get();
return resources;
}
// 用来模拟取order list的过程
public static String getOrder() throws InterruptedException {
Thread.sleep(2000L); // mock IO cost
return "order list";
}
// 用来模拟取user list的过程
public static String getUser() throws InterruptedException {
Thread.sleep(5000L); // mock IO cost
return "user list";
}
- getResource()方法返回:order list, user list
throwIfFailed()
还可以接收参数Function<Throwable, ? extends X> esf
,即可以新定义一个Function来处理异常。 比如:
typescript
public Function<Throwable, IllegalArgumentException> handleError(String message) {
return throwable -> {
log.error("do something here...");
return new IllegalArgumentException(message);
};
}
使用:
csharp
scope.join().throwIfFailed(handleError("invalid error"));
4.2 ShutdownOnSuccess
示例
与ShutdownOnFailure
不同的是,前者会等所有的subtask结束后待待进行汇总,而ShutdownOnSuccess
只需要一个subtask成功即会返回这个subtask的结果。
比如想要进行list sort排序,我们可以启用两个subtask,实现两个sort,这样只需要最快的那个返回,即视作成功。代码如下:
csharp
public String getResources() throws InterruptedException, ExecutionException {
try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {
scope.fork(() -> sort1());
scope.fork(() -> sort2());
scope.join();
return scope.result();
}
}
public static String sort1() throws InterruptedException {
Thread.sleep(2000L); // mock IO cost
return "sort1";
}
public static String sort2() throws InterruptedException {
Thread.sleep(1000L); // mock IO cost
return "sort2";
}
getResources()结果打印:sort2。