TGViewer
Java Geek Java Geek @java_geek · 2.37K subscribers
Post #525 187
🏗 Java: Структурная конкурентность. Прощайте, зомби-потоки!

Допустим, вы используете Виртуальные потоки (Project Loom), чтобы сделать два независимых запроса: получить данные пользователя (API 1) и его заказы (API 2).

Раньше мы использовали ExecutorService или CompletableFuture. Но у них есть огромная архитектурная дыра: они ничего не знают друг о друге.

🧟‍♂️ Проблема зомби-потоков (Unstructured Concurrency)

Если API 1 мгновенно падает с ошибкой 500, ваш метод все равно будет ждать, пока API 2 доработает (или упадет по таймауту). Поток, качающий заказы, становится "сиротой". Он делает бесполезную работу, тратит сеть и память, хотя результат уже никому не нужен.

А если ошибку выкинет родительский метод? Дочерние потоки продолжат жить своей жизнью в фоне. Это хаос.

🧩 Решение: StructuredTaskScope

В современной Java (начиная с 21 версии) потоки привязали к лексической области видимости - блоку кода. Если мы выходим из блока (из-за ошибки или успешного завершения), все запущенные внутри него дочерние потоки автоматически отменяются (получают interrupt).

Вот как выглядит "Запрос-Ответ", где должны выполниться оба действия (Стратегия *All or Nothing*):


// ShutdownOnFailure: если один упал, отменяем остальные
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {

// Запускаем подзадачи (Subtasks)
Subtask<User> user = scope.fork(() -> fetchUser(id));
Subtask<List<Order>> orders = scope.fork(() -> fetchOrders(id));

scope.join(); // Ждем завершения обеих задач...
scope.throwIfFailed(); // ...или первой же ошибки!

// Сюда дойдем, только если обе задачи успешны
return new UserProfile(user.get(), orders.get());
}
// При выходе из блока любые зависшие потоки будут убиты



🏎 Стратегия "Кто первый, тот и прав"

А что, если вам нужно получить курс валют, и у вас есть 3 разных провайдера? Вам нужен ответ от любого, кто ответит быстрее.


// ShutdownOnSuccess: первый успешный отменяет остальные
try (var scope = new StructuredTaskScope.ShutdownOnSuccess<Quote>()) {

scope.fork(() -> getFromBankA());
scope.fork(() -> getFromBankB());
scope.fork(() -> getFromBankC());

scope.join(); // Ждем первого успешного

return scope.result(); // Возвращаем самый быстрый ответ
}



Как только Банк А ответит, запросы к Банкам B и C будут немедленно отменены. Никакого ручного управления Future.cancel(). Всё работает из коробки.

🧠 Почему это меняет всё?

1. Читаемость: Многопоточный код читается сверху вниз, как обычный синхронный.
2. Безопасность ресурсов: Утечки потоков физически невозможны. Структура гарантирует, что родитель не завершится, пока не разберется со всеми детьми.
3. Идеальные логи: Стек-трейс теперь показывает реальную иерархию (кто кого вызвал), а не обрывается на внутренностях пула потоков.

В связке с Виртуальными потоками это делает Java одним из самых удобных языков для написания высоконагруженных сетевых приложений.

#Concurrency #ProjectLoom #CleanCode #Backend

📲 Мы в MAX

👉 @java_geek
  • 👍 1
  • 🔥 1
More from @java_geek
  1. Sep 24, 2026Что такое стек-трейс? Стек-трейс (stack trace) представляет собой список вызовов методов в…
  2. Sep 17, 2026Как перебрать элементы LinkedList в обратном порядке, не используя медленный get(index)? Д…
  3. Sep 15, 2026Array vs ArrayList Выбор между Array (стандартным Java-массивом) и ArrayList зависит от сп…
  4. Sep 11, 2026Какова цель ключевого слова final, когда оно используется с переменной? Ключевое слово fin…
  5. Sep 9, 2026☕ Java Tip: Как работает var в Java С версии Java 10 появился ключевое слово var. Оно упро…
  6. Sep 7, 2026Что такое перегрузка методов в Java? Перегрузка методов — это мощный приём, который позвол…
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 →