Что такое 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, ожидается стабилизация.