Что такое CompletableFuture и чем он отличается от Future
Главное отличие от обычного Future:
🟢 Junior Level
CompletableFuture — это класс в Java (появился в Java 8), который представляет собой результат асинхронной операции, который ещё не готов, но будет готов в будущем.
Главное отличие от обычного Future:
Future— только ожидание результата (get()блокирует поток)CompletableFuture— можно строить цепочки действий, комбинировать, обрабатывать ошибки
// Future — нужно ждать и блокировать поток
Future<String> future = executor.submit(() -> "Hello");
String result = future.get(); // блокирует поток!
// CompletableFuture — можно построить цепочку
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> "Hello");
cf.thenApply(s -> s + " World")
.thenAccept(System.out::println); // не блокирует!
Простая аналогия:
Future— как чек в ресторане: ждёте, пока еда будет готоваCompletableFuture— как доставка: заказали, и вам привезут, а вы занимаетесь своими делами
🟡 Middle Level
Как это работает
Future ограничения:
// ❌ Future — нельзя построить цепочку
Future<Integer> future = executor.submit(() -> 42);
// Нужно блокировать и обрабатывать вручную
try {
Integer result = future.get(); // блокирует
Integer doubled = result * 2;
} catch (InterruptedException | ExecutionException e) {
// ручная обработка
}
CompletableFuture возможности:
// ✅ Цепочка действий
CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(() -> 42);
cf.thenApply(result -> result * 2) // трансформация
.thenAccept(doubled -> System.out.println(doubled)) // потребление
.exceptionally(ex -> { // обработка ошибок
System.err.println("Error: " + ex);
return null;
});
// Не блокирует основной поток!
Создание:
// supplyAsync — с возвратом значения
CompletableFuture<String> cf1 =
CompletableFuture.supplyAsync(() -> "Hello");
// runAsync — без возврата
CompletableFuture<Void> cf2 =
CompletableFuture.runAsync(() -> System.out.println("Done"));
Типичные ошибки
- Забыли Async — выполняется в том же потоке: ```java CompletableFuture.supplyAsync(() -> “Hello”) .thenApply(s -> { // Выполняется в ForkJoinPool.commonPool() return s + “ World”; }); // Это НЕ ошибка, а осознанный выбор. thenApply в том же потоке — // правильный выбор для лёгких CPU-трансформаций. Проблема только если // thenApply выполняет блокирующую операцию.
// vs
CompletableFuture
2. **Не обработали исключения:**
```java
// ❌ Исключение потеряно
CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("Error");
}).thenApply(s -> s.toUpperCase());
// ✅ Обработка
CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("Error");
}).exceptionally(ex -> "default");
🔴 Senior Level
Internal Implementation
CompletableFuture vs Future:
// Future — interface
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit) throws ...;
}
// CompletableFuture — реализация + CompletionStage
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
// CompletionStage предоставляет 40+ методов для композиции
}
Internal structure:
// CompletableFuture использует lock-free алгоритм
// CAS (Compare-And-Swap) для завершения
// Stack of completions/dependencies для цепочек
class CompletableFuture<T> {
volatile Object result; // либо значение, либо AltResult (exception)
// AltResult — внутренний JDK-класс-обёртка для исключений.
// Позволяет хранить null как валидный результат и отличать его от исключения.
volatile Completion stack; // стек зависимостей
// CAS для завершения
boolean completeValue(T t) {
// compareAndSwap для установки result
}
}
Архитектурные Trade-offs
| Future | CompletableFuture |
|---|---|
| Блокирующий get() | Неблокирующий |
| Нет обработки ошибок | Полная обработка |
| Нельзя комбинировать | thenCombine, allOf, anyOf |
| ExecutorService нужен | Свой ForkJoinPool по умолчанию |
Edge Cases
1. Default Executor:
// CompletableFuture использует ForkJoinPool.commonPool()
// Для I/O операций — плохо (мало потоков)
CompletableFuture.supplyAsync(() -> {
// I/O операция — нужно больше потоков!
return httpClient.get(url);
});
// ✅ Свой Executor
ExecutorService ioExecutor = Executors.newFixedThreadPool(20);
CompletableFuture.supplyAsync(() -> httpClient.get(url), ioExecutor);
2. Exception propagation:
// Исключение оборачивается в CompletionException
CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("Original");
}).thenApply(s -> s.toUpperCase())
.exceptionally(ex -> {
// ex — CompletionException с оригинальной причиной
Throwable cause = ex.getCause(); // "Original"
return null;
});
Производительность
Операция | Future | CompletableFuture
----------------------|--------|------------------
Создание | 5 ns | 10 ns
get() (блокирующий) | 100μs+ | 100μs+
thenApply (цепочка) | N/A | 5-10 ns
Обработка ошибок | Manual | Встроенная
ForkJoinPool.commonPool():
- Размер = availableProcessors - 1
- Для CPU-bound задач — OK
- Для I/O — нужен свой Executor
Production Experience
Async API call:
@Service
public class UserService {
private final RestTemplate restTemplate;
private final ExecutorService executor;
public CompletableFuture<User> getUserAsync(Long userId) {
return CompletableFuture.supplyAsync(() -> {
return restTemplate.getForObject(
"http://api/users/" + userId, User.class
);
}, executor);
}
// Комбинирование нескольких вызовов
public CompletableFuture<UserProfile> getProfile(Long userId) {
CompletableFuture<User> user = getUserAsync(userId);
CompletableFuture<Order> lastOrder = getLastOrderAsync(userId);
return user.thenCombine(lastOrder, (u, o) ->
new UserProfile(u, o)
);
}
}
Best Practices
// ✅ Всегда обрабатывайте исключения
cf.exceptionally(ex -> defaultValue);
// ✅ Используйте свой Executor для I/O
CompletableFuture.supplyAsync(task, ioExecutor);
// ✅ Не блокируйте без необходимости
// ❌ cf.get() — только если действительно нужно
// ❌ Не игнорируйте CompletableFuture
// ❌ Не используйте commonPool для I/O
🎯 Шпаргалка для интервью
Обязательно знать:
- CompletableFuture появился в Java 8, реализует Future + CompletionStage
- CompletionStage предоставляет 40+ методов для композиции
- По умолчанию используется ForkJoinPool.commonPool() (availableProcessors - 1)
- supplyAsync() — с возвратом значения, runAsync() — без
- Исключения оборачиваются в CompletionException
- thenApply/thenAccept/thenRun — для цепочек, thenCombine/allOf/anyOf — для комбинирования
Частые уточняющие вопросы:
- Чем отличается от Future? — Future только блокирующий get(), CompletableFuture — неблокирующий с цепочками и обработкой ошибок
- Какой пул по умолчанию? — ForkJoinPool.commonPool(), для I/O нужен свой Executor
- Как обработать ошибку? — exceptionally(), handle(), whenComplete()
- Можно ли завершить вручную? — Да, complete() или completeExceptionally()
Красные флаги (НЕ говорить):
- «CompletableFuture блокирует поток» — он неблокирующий по дизайну
- «Использую commonPool для HTTP запросов» — thread pool starvation
- «Игнорирую CompletableFuture не обрабатывая» — потерянное исключение
Связанные темы:
- [[2. Какие основные преимущества CompletableFuture перед Future]]
- [[12. Какой пул потоков используется по умолчанию для async методов]]
- [[6. Как обрабатывать исключения в цепочке CompletableFuture]]
- [[16. Что делает метод supplyAsync() и когда его использовать]]