Как мы с вами знаем, в Java 19 был принят JEP-428, который является частью Project Loom и добавляет долгожданную Structured concurrency, которая добавит, например, возможность прервать несколько логически связанных потоков. Что сделает многопоточность в Java более дружелюбной для разработчиков.
Посмотрим доступные сборки.
Скачаем и распакуем:
wget https://download.java.net/java/early_access/jdk19/26/GPL/openjdk-19-ea+26_linux-x64_bin.tar.gz
sudo tar -C /lib/jvm -xvf /home/nkonev/Downloads/openjdk-19-ea+26_linux-x64_bin.tar.gz
Создадим файл
touch /home/nkonev/javaWorkspace/jdk19example/src/Main.java
С содержимым
import jdk.incubator.concurrent.StructuredTaskScope;
import java.time.Duration;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("Hello world!");
var main = new Main();
var start = System.currentTimeMillis();
var handle = main.handle();
var end = System.currentTimeMillis();
System.out.println("Got result " + handle + " in " + (end - start) + " millis");
}
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());
}
}
String findUser() throws InterruptedException {
Thread.sleep(Duration.ofMillis(300));
return "A user";
}
Integer fetchOrder() throws InterruptedException {
Thread.sleep(Duration.ofMillis(700));
return 123;
}
}
record Response(String user, Integer order) { }
Запустим его без компиляции
/lib/jvm/jdk-19/bin/java --enable-preview --add-modules jdk.incubator.concurrent --source 19 /home/nkonev/javaWorkspace/jdk19example/src/Main.java
Получим
WARNING: Using incubator modules: jdk.incubator.concurrent
warning: using incubating module(s): jdk.incubator.concurrent
1 warning
Hello world!
Got result Response[user=A user, order=123] in 713 millis
Отлично, позитивный сценарий работает.
Создадим второй файл Main2.java с негативным сценарием.
import jdk.incubator.concurrent.StructuredTaskScope;
import java.time.Duration;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class Main2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("Hello world!");
var main = new Main2();
var start = System.currentTimeMillis();
var handle = main.handle();
var end = System.currentTimeMillis();
System.out.println("Got result " + handle + " in " + (end - start) + " millis");
}
Response2 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 Response2(user.resultNow(), order.resultNow());
}
}
String findUser() throws InterruptedException {
Thread.sleep(Duration.ofMillis(300));
throw new RuntimeException("Boom!");
}
Integer fetchOrder() throws InterruptedException {
Thread.sleep(Duration.ofMillis(700));
System.out.println("fetchOrder completed");
return 123;
}
}
record Response2(String user, Integer order) { }
Обратите внимание, findUser() спит 300 миллисекунд и бросает эксепшн. Метод fetchOrder() спит 700 миллисекунд, затем пишет в лог "fetchOrder completed", затем отдаёт ответ.
Согласно этому JEP'у, строке scope.throwIfFailed(), из-за ошибки в findUser() метод fetchOrder() должен будет прерваться во время сна и не писать "fetchOrder completed". Проверим это:
/lib/jvm/jdk-19/bin/java --enable-preview --add-modules jdk.incubator.concurrent --source 19 /home/nkonev/javaWorkspace/jdk19example/src/Main2.java
получим:
WARNING: Using incubator modules: jdk.incubator.concurrent
warning: using incubating module(s): jdk.incubator.concurrent
1 warning
Hello world!
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.RuntimeException: Boom!
at jdk.incubator.concurrent/jdk.incubator.concurrent.StructuredTaskScope$ShutdownOnFailure.throwIfFailed(StructuredTaskScope.java:1125)
at Main2.handle(Main2.java:23)
at Main2.main(Main2.java:12)
Caused by: java.lang.RuntimeException: Boom!
at Main2.findUser(Main2.java:32)
at Main2.lambda$handle$0(Main2.java:19)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at java.base/java.lang.VirtualThread.run(VirtualThread.java:287)
at java.base/java.lang.VirtualThread$VThreadContinuation.lambda$new$0(VirtualThread.java:174)
at java.base/jdk.internal.vm.Continuation.enter0(Continuation.java:327)
at java.base/jdk.internal.vm.Continuation.enter(Continuation.java:320)
Работает!
Отлично, теперь, ждём и надеемся что столь полезную фичу для веб-разработчиков доведут до релиза.
Бонус - как запустить это в IntelliJ IDEA (Build system - IntelliJ)
Результат:
Бонус 2
Похожая статья на InfoQ