Gymterview
senior

Что такое Structured Concurrency (StructuredTaskScope) и зачем это нужно?

Structured Concurrency (структурированная конкуренция) — это подход к многопоточному программированию, в котором жизненный цикл параллельных задач привязан к области видимости кода, их породившего (JEP 462, preview в Java 21-23). Основная идея: если задача порождает подзадачи, все они должны завершиться до завершения родительской задачи.

Проблема неструктурированной конкуренции:

Пример
// Unstructured concurrency — поток может «убежать»
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
Future<String> user = executor.submit(() -> fetchUser(id));
Future<String> orders = executor.submit(() -> fetchOrders(id));
// Что если fetchUser() упал? fetchOrders() продолжает работать зря!
// Что если текущий поток был прерван? Подзадачи продолжают работать!

Это приводит к: утечкам потоков, сложности отладки, неполной обработке ошибок.

Решение через StructuredTaskScope:

Пример
String handle(String userId) throws ExecutionException, InterruptedException {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        Subtask<String> user = scope.fork(() -> fetchUser(userId));
        Subtask<String> orders = scope.fork(() -> fetchOrders(userId));

        scope.join();            // Ожидание завершения всех подзадач
        scope.throwIfFailed();   // Если любая подзадача упала — бросаем исключение

        return user.get() + " : " + orders.get();
    }
    // При выходе из try-with-resources все незавершённые подзадачи отменяются
}

Стратегии завершения:

Стратегия Поведение Паттерн
ShutdownOnFailure При ошибке в любой подзадаче — все остальные отменяются «Всё или ничего»
ShutdownOnSuccess При успехе любой подзадачи — все остальные отменяются «Первый успешный»
Код: получение первого успешного результата
try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {
    scope.fork(() -> fetchFromServer1());
    scope.fork(() -> fetchFromServer2());
    scope.fork(() -> fetchFromServer3());

    scope.join();
    String fastest = scope.result(); // результат первого завершившегося
}

Гарантии Structured Concurrency:

  • Все дочерние потоки завершатся до выхода из try-with-resources.
  • Стек вызовов дочерних потоков содержит информацию о родительском scope — упрощает отладку.
  • StructuredTaskScope предназначен для использования с Virtual Threads — каждая fork() создаёт виртуальный поток.

Частые ошибки:

  • fork() после join() — приведёт к исключению.
  • Забытый join() — задачи могут не завершиться.
  • Использование для длительных фоновых задач — это инструмент для короткоживущих параллельных подзадач.

Аналогия: Structured Concurrency — это правила воспитания. Родитель (scope) отправляет детей (подзадачи) гулять, но обязан дождаться, пока все вернутся, прежде чем закрыть дверь (выйти из блока). Если один ребёнок попал в беду (ошибка) — родитель зовёт всех обратно (shutdown).

На собеседовании хороший ответ включает мотивацию: «Structured Concurrency делает для потоков то, что структурное программирование сделало для goto — превращает хаос в предсказуемую структуру». Также упомяните, что это preview feature, ожидается стабилизация.