Структурированный параллелизм (Structured Concurrency)

Тема дорожной карты · Java

Структурированный параллелизм (structured concurrency) — API из Project Loom, переносящий принцип структурного программирования на многопоточность: группа связанных подзадач запускается, живёт и завершается строго внутри одного блока кода. Впервые появился как preview в Java 21 (JEP 453), а в Java 25 (JEP 505, пятый preview) API радикально переработали: вместо публичных конструкторов и вложенных классов ShutdownOnFailure/ShutdownOnSuccess теперь есть статическая фабрика StructuredTaskScope.open() и интерфейс Joiner, задающий политику завершения. Шестой и седьмой preview (JEP 525 в Java 26 и JEP 533 в Java 27) шлифуют детали, но форма API держится. Функциональность всё ещё preview: для компиляции и запуска нужен флаг --enable-preview.

Как это работает

Scope открывается в try-with-resources, подзадачи создаются через fork() — каждая в новом виртуальном потоке, а join() ждёт итог по выбранной политике:

try (var scope = StructuredTaskScope.open()) {
    var user  = scope.fork(() -> userService.find(id));   // Subtask<User>
    var order = scope.fork(() -> orderService.find(id));  // Subtask<Order>
    scope.join();  // ждёт всех; при сбое одной — отменяет остальные
    return render(user.get(), order.get());
}

Политика по умолчанию: все подзадачи обязаны завершиться успешно. Если одна упала, остальные отменяются, а join() бросает FailedException с исходной причиной — ошибка не теряется. Другие политики задаются реализациями Joiner: например, StructuredTaskScope.Joiner.anySuccessfulResultOrThrow() вернёт из join() первый успешный результат (гонка зеркальных источников), а awaitAll() дождётся всех независимо от исхода. Ключевые гарантии модели: подзадача не может пережить свой scope, отмена распространяется каскадом на всю иерархию вложенных областей, а в дампе потоков видно дерево «родитель — подзадачи», а не плоский список, что сильно упрощает диагностику.

Мотивация модели: при ручном запуске фоновых задач родитель может завершиться раньше своих детей, отмена легко забывается, а исключение из фоновой задачи теряется в никуда. Структурная модель устраняет целый класс подобных утечек синтаксически — выйти из блока, оставив живую подзадачу, просто невозможно. Дополнительно перегрузка open() принимает конфигурацию, в которой задаётся таймаут на весь блок: по его истечении незавершённые подзадачи отменяются так же, как при сбое.

Когда применять

Типичные ошибки

Связанные понятия

Полезные ресурсы

Проверить знания (2)

Загрузка вопросов…