TGViewer
Spring АйО Spring АйО @spring_aio · 10.9K subscribers
Post #1043 7K
👨‍💻 Structured Concurrency в Java 26 — шестое превью. JEP 525.

Проблема, которую решает эта фича, хорошо знакома любому, кто писал параллельный код на Java. ExecutorService, Future, CompletableFuture ничего не знают о связях между задачами. Три параллельные подзадачи для одного запроса живут в разных потоках без общего «родителя» — и если одна упала, об этом никто автоматически не узнает.

Классический пример: параллельно загружаем профиль, настройки и историю пользователя.


public class UnstructuredExample {

public static UserData loadUserData(int userId) {
String profile = fetchProfile(userId);
String preferences = fetchPreferences(userId);
String history = fetchHistory(userId);
return new UserData(profile, preferences, history);
}
}


Можно, конечно, попробовать добиться этого поведения вручную: добавить cancel() в catch, завернуть всё в CompletableFuture.allOf, аккуратно обработать CompletionException. Но очень легко сделать что-то не так. И чем больше задач — тем больше бойлерплейта, который всё равно не даёт нормальной иерархии, нормальных стектрейсов и легко читаемого кода.

Structured Concurrency решает это на уровне API.


public class StructuredExample {

public static UserData loadUserData(int userId) {
try (var scope = StructuredTaskScope.open(
StructuredTaskScope.Joiner.allSuccessfulOrThrow(),
Configuration cfg -> cfg
.withTimeout(Duration.ofSeconds(5))
.withName("load-user-data"))) {

// Fork all three subtasks — they run concurrently
var profile = scope.fork(() -> fetchProfile(userId));
var preferences = scope.fork(() -> fetchPreferences(userId));
var history = scope.fork(() -> fetchHistory(userId));

scope.join();

return new UserData(profile.get(),
preferences.get(),
history.get()
);
} catch (StructuredTaskScope.FailedException e) {
throw new RuntimeException("Failed to load user data: " + e.getCause().getMessage(), e);
}
}
}


Согласитесь, круто? Если любая задача упала — остальные отменяются автоматически. Поток-владелец гарантированно переживает все дочерние. Стектрейсы отражают реальную иерархию вызовов. Время жизни задач привязано к лексическому блоку — как try-with-resources. И, что самое главное, описанное выше поведение можно довольно легко настроить.

Данная функциональность, на самом деле, с нами уже довольно давно. А что же поменялось в Java 26 по сравнению с Java 25?

— скоуп создаётся через статический StructuredTaskScope.open() вместо new
— join() возвращает List вместо Stream — результаты материализованы сразу, без риска обратиться к ним после закрытия скоупа
— добавился joinUntil(deadline) — если задачи не успели к дедлайну, скоуп их отменяет

API явно стабилизируется, но одному Гослингу известно сколько еще итераций preview ждёт эта фича 🙂

Подробнее про Java 26 можно почитать и посмотреть в отдельном видео и статье на Хабре.

@spring_aio
  • 🔥 49
  • 👍 19
  • ❤ 11
  • ⚡ 3
More from @spring_aio
  1. Oct 3, 2026⚡️⚡️Строки кода стремятся подешеветь до цены токена. Что остается ценным? Про это — трек «…
  2. Oct 2, 2026🙅‍♂️ Не каждый AI-вызов должен генерировать текст Иногда от модели нужен не красивый отве…
  3. Oct 1, 2026JEP 544: JVM будет сохранять машинный код между запусками JEP 544 (Ahead-of-Time Code Comp…
  4. Sep 30, 2026🍃 Переписываем всё на Rust, JavaScript — кошмар, AI против Vaadin | Spring АйО Подкаст №7…
  5. Sep 29, 2026⚡️ AI-плагин уже в OpenIDE Pro Вышли новые версии OpenIDE и OpenIDE Pro: AI-плагин теперь…
  6. Sep 28, 2026🧑‍💻 Скрытая возможность конфигурации Spring Boot, о которой мало говорят Представим, что…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →