Всем привет, решил более подробно "поковырять" многопоточность, ее изменения и тренды. Далее думаю выпустить пару постов на разбор задач на рефакторинг.
Мы привыкли, что многопоточность - это классическая борьба с блокировками потоков и бесконечные синхронайзы, но эпоха реактивного программирования (Project Reactor, RxJava) на внутреннем бэкенде подходит к концу. С выходом Project Loom в JDK 21 и развитием Structured Concurrency в JDK 23–24, Java-разработчикам больше не нужно выбирать между простотой блокирующего кода и производительностью асинхронного.
Если в проекте до сих пор используется
newFixedThreadPool для сетевых вызовов — мы платим производительностью за привычки 2010-х годов. Вот что нужно знать о современной конкурентности в Java.1. Virtual Threads: «Один запрос — один поток» возвращается
Виртуальные потоки (Virtual Threads) — это легковесные потоки, которые не привязаны к потокам операционной системы.
Зачем это нужно: Классический
newFixedThreadPool(200) упирается в лимит ОС при 10 000 одновременных запросов. Виртуальные потоки позволяют запустить миллион потоков в одном JVM без деградации.Где применять: I/O-bound задачи. Вызовы к базам данных (JDBC), HTTP-клиенты, RPC. Код остается простым и синхронным (
try-with-resources, обычные циклы), но под капотом JVM ставит поток на паузу, пока он ждет сеть, освобождая ресурсы.Важное правило: Не пулируйте виртуальные потоки. Используйте
Executors.newVirtualThreadPerTaskExecutor(). Создавать их дешево, переиспользование больше не требуется.2. Structured Concurrency
Обычный
ExecutorService легко порождает «сиротские» задачи: родительский запрос отменился из-за таймаута, а фоновый HTTP-вызов к CRM продолжает висеть в thread dump. Structured Concurrency решает эту проблему, заставляя группу потоков вести себя как единый блок.Как это работает: Вы создаете область (scope), запускаете в ней задачи и ждете их все разом. Если одна задача падает или родительский поток отменяется, остальные задачи в этой области автоматически прерываются. Никаких утечек, никаких «зомби-потоков».
Пример: Обработка заказа, где нужно параллельно сходить в инвентарь и службу лояльности. Если инвентарь ответил ошибкой, нет смысла ждать ответ от лояльности — транзакция всё равно откатится.
3. Scoped Values
ThreadLocal — главный источник утечек памяти и проблем при пуллинге потоков. В мире виртуальных потоков, которые создаются и уничтожаются миллионами, ThreadLocal становится токсичным.Решение: Scoped Values (стабильны с JDK 25). Это неизменяемая передача контекста. Значение доступно только в рамках текущего метода и его дочерних вызовов. Оно не требует ручной очистки в
finally-блоке и безопасно для виртуальных потоков.4. Подводные камни
Переход на Loom не является бесплатным апгрейдом:
Pinning (Прибивание): Если виртуальный поток заходит в блок
synchronized или вызывает нативный код, он «прибивается» к потоку ОС и не может его освободить. В JDK 24 проблема synchronized практически решена, но старые драйверы БД всё еще могут стать узким местом.Бенчмарки CPU: Виртуальные потоки не делают ваш код быстрее, если он нагружает процессор (вычисления, хэширование). Для CPU-bound задач по-прежнему используйте
ForkJoinPool или фиксированные пулы.Инструментарий: Профилировщики и дебаггеры (особенно в версиях 21–23) могут работать медленнее или показывать странные стек-трейсы из-за механизма continuations.
Итог: Современный энтерпрайз-Java в 2026 году — это синхронный, легко читаемый код, который масштабируется лучше, чем сложные реактивные пайплайны. Умение грамотно применять
newVirtualThreadPerTaskExecutor и StructuredTaskScope — это маркер Senior-разработчика, готового к высоким нагрузкам.
Просмотры: 8