Асинхронность в Java
Асинхронность в Java
Асинхронность — «запустил задачу и не жду у стены, пока она закончится». В Java для этого есть несколько уровней — от тяжёлых потоков ОС до лёгких virtual threads (Java 21+).
Эта статья — практический выбор инструмента, не дублирование JVM и потоков (там память, synchronized, GC).
| Ваша задача | С чего начать |
|---|---|
| Параллельно посчитать отчёт | ExecutorService с ограниченным пулом |
| Несколько HTTP/БД вызовов в одном запросе | CompletableFuture или virtual threads |
| Миллионы одновременных блокирующих I/O | Virtual threads (Java 21+) |
| Весь стек reactive end-to-end | WebFlux — Spring |
На Kotlin те же идеи проще выражаются — корутины.
Thread и Runnable
Поток ОС — тяжёлый ресурс (стек, планировщик). Создание «на каждый запрос» не масштабируется.
Thread t = new Thread(() -> {
System.out.println(Thread.currentThread().getName());
});
t.start();
t.join();
Интерфейс Runnable (или Callable<V> с результатом) описывает задачу; поток её выполняет.
ExecutorService
Пул переиспользует ограниченное число потоков для множества задач:
ExecutorService pool = Executors.newFixedThreadPool(4);
Future<Integer> future = pool.submit(() -> compute());
Integer result = future.get(5, TimeUnit.SECONDS);
pool.shutdown();
| Фабрика | Поведение |
|---|---|
newFixedThreadPool(n) | Ровно n рабочих потоков, очередь задач |
newCachedThreadPool() | Потоки по требованию, риск раздувания при всплеске |
newSingleThreadExecutor() | Одна очередь, порядок FIFO |
В production предпочтительно явно задавать размер пула и политику очереди (ThreadPoolExecutor), а не безлимитный cached.
CompletableFuture
Композиция асинхронных шагов без ручного wait/notify:
ExecutorService ioPool = Executors.newFixedThreadPool(8);
CompletableFuture<User> userFuture = CompletableFuture
.supplyAsync(() -> loadUser(id), ioPool);
CompletableFuture<List<Order>> ordersFuture = CompletableFuture
.supplyAsync(() -> loadOrders(id), ioPool);
CompletableFuture<Profile> profileFuture = userFuture
.thenCombine(ordersFuture, Profile::new);
Profile profile = profileFuture
.orTimeout(3, TimeUnit.SECONDS)
.exceptionally(ex -> Profile.empty())
.join();
Полезные методы:
thenApply/thenAccept/thenRun— продолжение с результатом / без / void;thenCompose— когда следующий шаг тоже возвращаетCompletableFuture;allOf/anyOf— ожидание нескольких задач;completeOnTimeout,orTimeout— дедлайны.
Обработка ошибок: необработанное исключение завершает цепочку; используйте exceptionally, handle или whenComplete.
future.handle((result, ex) -> {
if (ex != null) {
log.error("failed", ex);
return fallback;
}
return result;
});
Не вызывайте join()/get() на UI-потоке и не блокируйте event loop без причины.
Virtual threads (Java 21+)
Виртуальный поток — лёгкий поток JVM, монтируемый на небольшой пул carrier-потоков ОС. Подходит для массового блокирующего I/O (HTTP-клиент, JDBC, файлы).
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i ->
executor.submit(() -> handleRequest(i))
);
}
Или напрямую:
Thread.startVirtualThread(() -> process(socket));
| Модель | Когда |
|---|---|
| Platform threads + фиксированный пул | CPU-bound, предсказуемая нагрузка, legacy |
| Virtual threads | Много одновременных блокирующих I/O операций |
CompletableFuture + явный пул | Композиция шагов, интеграция с async API |
| Reactive (WebFlux) | End-to-end неблокирующий стек, backpressure |
Virtual threads не ускоряют CPU-bound задачи: для вычислений по-прежнему нужен ограниченный пул platform threads или ForkJoinPool.
synchronized, volatile, Lock
Кратко (подробнее в JVM и потоки; демон-потоки и shutdown hook — там же):
synchronized— взаимное исключение и видимость для блока/метода;volatile— видимость записи без атомарности составных операций;ReentrantLock,ReadWriteLock— гибче, сtryLockи таймаутами;java.util.concurrent.atomic— счётчики и ссылки без блокировок.
Асинхронность не отменяет необходимость синхронизации общего изменяемого состояния.
parallelStream и async
parallelStream() делит данные между потоками ForkJoinPool — не путать с асинхронным I/O. См. Stream API.
Практические правила
- Разделяйте пулы для CPU и для I/O.
- Всегда задавайте таймауты на внешние вызовы.
- Закрывайте
ExecutorService(shutdown/shutdownNow) при остановке приложения. - Логируйте необработанные исключения в
whenComplete. - Для Spring Boot 3.2+ рассмотрите virtual threads через
spring.threads.virtual.enabled=trueна совместимых стеках.
Частые ошибки
| Ошибка | Последствие |
|---|---|
Новый Thread на каждый запрос | Исчерпание памяти и CPU |
get() без таймаута | Зависший поток навсегда |
| Virtual threads для CPU-bound | Не даёт выигрыша, нужен пул |
Общий ArrayList без синхронизации | Гонки и «случайные» баги |
Что попробовать
- Один сервис с
CompletableFuture.orTimeout(2, SECONDS). - Spring Boot 3.2+:
spring.threads.virtual.enabled=trueи сравнить нагрузку. - Диагностика JVM при «всё тормозит» под нагрузкой.
Дальше
JVM и потоки · Stream API · Spring · Kotlin coroutines
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). Основы Java - устройство JDK/JVM, модель компиляции и базовые принципы платформонезависимого выполнения. Java — это объектно-ориентированный язык программирования общего назначения, который работает на принципах «напиши один раз, запускай в любом месте». Набор советов, правил, принципов и обычаев в разработке на этом языке. История Java — от проекта Green и Oak до OpenJDK, LTS-релизов и современной платформы (модули, records, виртуальные потоки). Библиотеки, фреймворки, инструменты сборки, тестирования, развёртывания и мониторинга. Что такое пакет и пакетная структура, как собираются проекты на Java. Справочник-шпаргалка по конфигурациям в Java — типы, синтаксис, стандартная библиотека, типовые паттерны. Не заменяет пошаговое обучение. Учебный курс — раздел. Гайд по установке и настройке с написанием первой программы и её запуском. Практические примеры — консольные утилиты, композиция классов в мини-игре и первое Swing-приложение. Точки останова, пошаговое выполнение, панели Variables и Call Stack — практика отладки в IntelliJ IDEA. Кавычки, точки, запятые, скобки и прочие знаки препинания. Это полный справочник всех ключевых слов языка Java, включая основные, контекстные и зарезервированные слова.Основы языка Java
Что требуется знать перед началом изучения языка программирования Java
Рекомендации по разработке на Java
История языка Java
Экосистема Java-приложений
Структура и сборки Java-проектов
Справочник по конфигурациям в Java
Первая программа на Java
Простые приложения на Java
Отладка Java-кода в IDE
Синтаксис и пунктуация в Java
Ключевые слова в Java