Что такое CompletableFuture и как он работает?
CompletableFuture — это класс из java.util.concurrent (Java 8), реализующий интерфейсы Future и CompletionStage. Он представляет результат асинхронного вычисления, который можно явно завершить, и предоставляет богатый API для построения цепочек асинхронных операций.
Отличие от обычного Future: Future (Java 5) позволяет лишь проверить завершённость и получить результат блокирующим вызовом get(). CompletableFuture добавляет:
- Неблокирующие цепочки операций (
thenApply,thenCompose) - Комбинирование нескольких задач (
thenCombine,allOf,anyOf) - Обработку ошибок в функциональном стиле (
exceptionally,handle) - Возможность ручного завершения (
complete(),completeExceptionally())
Принцип работы — «обещание» (promise):
Пример
// Создание и ручное завершение
CompletableFuture<String> future = new CompletableFuture<>();
// В другом потоке:
future.complete("Результат");
// Или при ошибке:
future.completeExceptionally(new RuntimeException("Ошибка"));
// Создание с автоматическим выполнением в ForkJoinPool.commonPool()
CompletableFuture<String> asyncFuture = CompletableFuture.supplyAsync(() -> {
// длительная операция
return "Результат асинхронного вычисления";
});
// С явным пулом потоков
ExecutorService executor = Executors.newFixedThreadPool(4);
CompletableFuture<String> customPoolFuture = CompletableFuture.supplyAsync(() -> {
return "Результат в custom pool";
}, executor);
Построение цепочек:
Пример
CompletableFuture<String> result = CompletableFuture
.supplyAsync(() -> fetchDataFromDB()) // Асинхронно получить данные
.thenApply(data -> transform(data)) // Преобразовать результат
.thenApply(transformed -> format(transformed)) // Отформатировать
.exceptionally(ex -> "Значение по умолчанию"); // Обработать ошибку
Комбинирование нескольких CompletableFuture:
Пример
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Привет");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> " Мир");
// Комбинирование двух результатов
CompletableFuture<String> combined = future1.thenCombine(future2, (s1, s2) -> s1 + s2);
System.out.println(combined.get()); // "Привет Мир"
// Ожидание завершения всех
CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2);
// Ожидание первого завершённого
CompletableFuture<Object> any = CompletableFuture.anyOf(future1, future2);
Ключевые нюансы:
- По умолчанию используется
ForkJoinPool.commonPool(). Для IO-задач рекомендуется передавать собственныйExecutorService. - Методы с суффиксом
Async(например,thenApplyAsync) выполняют следующий шаг в отдельном потоке, без суффикса — в потоке, завершившем предыдущий шаг. CompletableFutureможно завершить вручную черезcomplete(), что удобно для интеграции с callback-based API.- Цепочки операций формируют направленный ациклический граф (DAG) зависимостей.
Частые ошибки:
get()без таймаута — может заблокировать поток навсегда. Используйтеget(timeout, unit)илиjoin().- Игнорирование исключений — без
exceptionally()илиhandle()исключение обнаружится только приget(). - Блокирующие IO в
ForkJoinPool.commonPool()— это общий пул, блокировка его потоков приводит к голоданию.
Актуальность в 2026: CompletableFuture остаётся основным инструментом для оркестрации асинхронных задач. С Virtual Threads (Java 21+) необходимость в нём для простых IO-задач снижается, но для сложных сценариев комбинирования он по-прежнему незаменим.
Аналогия:
CompletableFuture— это заказ в ресторане. Вы оформляете заказ (supplyAsync) и получаете номерок (объект future). Пока заказ готовится, вы можете добавить просьбу «когда будет готово — подогрейте» (thenApply), «если не получится — принесите альтернативу» (exceptionally). Когда блюдо готово, вся цепочка инструкций выполняется автоматически.
На собеседовании часто спрашивают: «В каком потоке выполняется
thenApply?» Ответ: в потоке, завершившем предыдущий этап, или в вызывающем потоке, если future уже завершён. Для гарантированного выполнения в пуле используйтеthenApplyAsync.