Какие основные преимущества CompletableFuture перед Future
CompletableFuture имеет 5 главных преимуществ перед обычным Future:
🟢 Junior Level
CompletableFuture имеет 5 главных преимуществ перед обычным Future:
- Не блокирует — можно строить цепочки без ожидания
- Комбинирование — можно объединять несколько CompletableFuture
- Обработка ошибок — встроенные методы для исключений
- Создание вручную — можно завершить результат в любой момент
- Больше методов — 40+ методов для разных сценариев
// Future — только ждать
Future<String> future = executor.submit(() -> "Hello");
String result = future.get(); // блокирует!
// CompletableFuture — цепочка без ожидания
CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World")
.thenAccept(System.out::println); // не блокирует!
🟡 Middle Level
Детальные преимущества
1. Composability (Компонуемость):
// Future — нельзя комбинировать
Future<Integer> f1 = executor.submit(() -> 10);
Future<Integer> f2 = executor.submit(() -> 20);
// Нужно ждать оба и складывать вручную
// CompletableFuture — thenCombine
CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<Integer> cf2 = CompletableFuture.supplyAsync(() -> 20);
cf1.thenCombine(cf2, (a, b) -> a + b)
.thenAccept(System.out::println); // 30
2. Обработка ошибок:
// Future — try/catch вручную
try {
Integer result = future.get();
} catch (ExecutionException e) {
// manual handling
}
// CompletableFuture — exceptionally
cf.exceptionally(ex -> {
log.error("Error", ex);
return defaultValue;
});
3. Manual completion:
// Можно завершить вручную
CompletableFuture<String> cf = new CompletableFuture<>();
// В другом потоке:
cf.complete("result"); // установить результат
cf.completeExceptionally(new RuntimeException("Error")); // ошибку
4. Async chain без блокировки:
// Future — блокирует на каждом шаге
String url = future1.get();
String data = future2.get();
Result result = process(url, data);
// CompletableFuture — вся цепочка асинхронная
supplyAsync(() -> getUrl())
.thenCompose(url -> fetchDataAsync(url))
.thenApply(data -> processData(data))
.thenAccept(result -> save(result));
5. Timeout поддержка:
// Java 9+
cf.orTimeout(5, TimeUnit.SECONDS);
cf.completeOnTimeout(defaultValue, 5, TimeUnit.SECONDS);
Типичные ошибки
- Не используют async методы: ```java // ❌ Выполняется в том же потоке cf.thenApply(s -> s.toUpperCase());
// ✅ Выполняется асинхронно cf.thenApplyAsync(s -> s.toUpperCase(), executor);
// thenApply в том же потоке — это НЕ баг, а intentional design.
// Используйте для лёгких трансформаций. Для blocking — только *Async.
---
## 🔴 Senior Level
### Internal Implementation
**CompletionStage interface:**
```java
// CompletableFuture implements CompletionStage<T>
// CompletionStage предоставляет 40+ методов:
// - thenApply, thenAccept, thenRun
// - thenCombine, thenCompose
// - allOf, anyOf
// - exceptionally, handle, whenComplete
// Каждый метод возвращает CompletionStage — можно строить цепочки
Non-blocking execution:
// CompletableFuture использует callback-based подход
// Вместо blocking get() — callback выполняется при завершении
class CompletableFuture<T> {
volatile Object result;
volatile Completion stack; // linked list of callbacks
// При завершении — все callbacks выполняются
void completeValue(T t) {
// CAS для установки result
// Потом выполняются все pending callbacks
}
}
Архитектурные Trade-offs
| Подход | Плюсы | Минусы |
|---|---|---|
| CompletableFuture | Неблокирующий, композируемый | Сложнее дебажить |
| Future + get() | Простой | Блокирующий |
| Reactive Streams | Backpressure, streaming | Сложнее |
| Virtual Threads | Простой код, non-blocking | Java 21+ |
Edge Cases
1. Exception wrapping:
// Исключения оборачиваются в CompletionException
cf.thenApply(s -> {
throw new RuntimeException("Original");
}).exceptionally(ex -> {
// ex — CompletionException
// ex.getCause() — оригинальное исключение
return null;
});
2. Cancellation:
// CompletableFuture.cancel() не прерывает выполнение
cf.cancel(true); // mayInterruptIfRunning игнорируется
// Нужно обрабатывать вручную
cf.obtrudeValue(null); // принудительно завершить
Производительность
// Порядок величин (зависит от JVM и workload):
// Callback: наносекунды, Async: микросекунды, Blocking: миллисекунды+
Production Experience
Microservice communication:
@Service
public class OrderService {
private final UserService userService;
private final InventoryService inventoryService;
private final PaymentService paymentService;
public CompletableFuture<Order> createOrder(CreateOrderRequest req) {
return userService.validateUserAsync(req.userId())
.thenCompose(user ->
inventoryService.checkStockAsync(req.items())
.thenCompose(stock ->
paymentService.processPaymentAsync(req.payment())
.thenApply(payment ->
new Order(user, stock, payment)
)
)
)
.exceptionally(ex -> {
log.error("Order creation failed", ex);
throw new OrderCreationException(ex);
});
}
}
Best Practices
// ✅ Цепочки вместо блокировки
cf.thenApply(...).thenAccept(...);
// ✅ Обработка ошибок
cf.exceptionally(ex -> defaultValue);
// ✅ Свой Executor для I/O
CompletableFuture.supplyAsync(task, ioExecutor);
// ✅ Комбинирование
cf1.thenCombine(cf2, (a, b) -> combine(a, b));
// ❌ Блокирующий get() без причины
// ❌ Игнорирование исключений
// ❌ commonPool для I/O операций
Когда НЕ использовать CompletableFuture
- Simple fire-and-forget — достаточно
executor.submit() - Virtual Threads (Java 21+) — часто проще писать синхронный код
- Стримы данных — лучше reactive (Flux/Mono)
🎯 Шпаргалка для интервью
Обязательно знать:
- Неблокирующая композиция — цепочки без блокировки get()
- Комбинирование — thenCombine, allOf, anyOf для параллельных вызовов
- Обработка ошибок — exceptionally, handle, whenComplete
- Manual completion — complete(), completeExceptionally()
- 40+ методов CompletionStage для любых сценариев
- Timeout поддержка — orTimeout(), completeOnTimeout() (Java 9+)
Частые уточняющие вопросы:
- Главное преимущество перед Future? — Неблокирующая композиция и обработка ошибок без get()
- Как комбинировать два CF? — thenCombine(cf2, combiner) для параллельных, thenCompose для зависимых
- Когда НЕ использовать? — Simple fire-and-forget, Virtual Threads (Java 21+), стримы данных (лучше Reactor)
- Как избежать блокировки? — Цепочки thenApply/thenAccept вместо get()/join()
Красные флаги (НЕ говорить):
- «Future и CompletableFuture interchangeable» — у Future нет цепочек и обработки ошибок
- «thenCompose и thenCombine это одно и то же» — thenCompose последовательный, thenCombine параллельный
- «Всегда использую commonPool» — для I/O это thread pool starvation
Связанные темы:
- [[1. Что такое CompletableFuture и чем он отличается от Future]]
- [[4. В чём разница между thenApply() и thenCompose()]]
- [[8. Как комбинировать результаты нескольких CompletableFuture]]
- [[24. В каких случаях лучше использовать CompletableFuture, а в каких - реактивное программирование]]