Как мы с вами знаем, в 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