Что делает метод supplyAsync() и когда его использовать
Structured Java interview answer with junior, middle, and senior-level explanation.
🟢 Junior Level
supplyAsync() — создаёт CompletableFuture, который выполняет задачу асинхронно и возвращает результат.
// supplyAsync — задача с возвратом значения
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
// Выполняется в другом потоке
return "Hello from async";
});
// Получить результат
cf.thenAccept(result -> System.out.println(result));
Отличие от runAsync():
supplyAsync()— возвращает значение (Supplier<T>)runAsync()— не возвращает (Runnable)
// supplyAsync — с результатом
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> "result");
// runAsync — без результата
CompletableFuture<Void> cf2 = CompletableFuture.runAsync(() -> {
System.out.println("Done");
});
🟡 Middle Level
Когда использовать
supplyAsync:
// Когда нужен результат
CompletableFuture<User> user = CompletableFuture.supplyAsync(() -> {
return userRepository.findById(userId);
});
CompletableFuture<String> data = CompletableFuture.supplyAsync(() -> {
return httpClient.get(url);
});
С своим Executor:
ExecutorService ioExecutor = Executors.newFixedThreadPool(10);
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
return httpClient.get(url);
}, ioExecutor);
Типичные ошибки
- Blocking в commonPool: ```java // ❌ Блокирует ForkJoinPool CompletableFuture.supplyAsync(() -> { return httpClient.get(url); // blocking I/O });
// ✅ Свой Executor CompletableFuture.supplyAsync(() -> httpClient.get(url), ioExecutor);
### Когда НЕ использовать supplyAsync
- **Простые синхронные операции** — overhead на создание CF не оправдан
- **Жёсткий порядок выполнения** — используйте последовательные вызовы
- **Нужна отмена с прерыванием** — лучше FutureTask
---
## 🔴 Senior Level
### Internal Implementation
```java
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return asyncSupplyStage(asyncPool, supplier);
}
public static <U> CompletableFuture<U> supplyAsync(
Supplier<U> supplier, Executor executor
) {
return asyncSupplyStage(screenExecutor(executor), supplier);
}
// asyncPool = ForkJoinPool.commonPool()
// Создаёт CompletableFuture и планирует задачу в executor
// Упрощённая псевдокод-репрезентация. Реальные имена методов в JDK отличаются.
Архитектурные Trade-offs
| supplyAsync | runAsync |
|---|---|
| Возвращает T | Возвращает Void |
| Supplier |
Runnable |
| Для вычислений | Для side effects |
Edge Cases
1. Exception in supplier:
CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("Error");
}).exceptionally(ex -> "recovered");
2. Long-running tasks:
// ❌ Долгая задача в commonPool
CompletableFuture.supplyAsync(() -> {
Thread.sleep(60000); // блокирует 60 секунд
return result;
});
// ✅ Свой Executor
CompletableFuture.supplyAsync(longTask, longTaskExecutor);
Performance
supplyAsync():
- Создание: ~10 ns
- Планирование: ~1μs
- Thread switch: ~1-5μs
commonPool overhead: минимальный
Custom executor: + queue overhead
Production Experience
@Service
public class OrderService {
private final ExecutorService ioExecutor;
public CompletableFuture<Order> getOrderAsync(Long id) {
return CompletableFuture.supplyAsync(() -> {
return orderRepository.findById(id);
}, ioExecutor);
}
// Комбинирование
public CompletableFuture<OrderSummary> getSummaryAsync(Long id) {
CompletableFuture<Order> order = getOrderAsync(id);
CompletableFuture<User> user = getUserAsync(id);
return order.thenCombine(user, (o, u) ->
new OrderSummary(o, u)
);
}
}
Best Practices
// ✅ supplyAsync для вычислений
CompletableFuture.supplyAsync(() -> calculate());
// ✅ Свой Executor для I/O
CompletableFuture.supplyAsync(ioTask, ioExecutor);
// ✅ Virtual Threads (Java 21+)
CompletableFuture.supplyAsync(task, vThreads);
// ❌ Blocking в commonPool
// ❌ Долгие задачи без своего Executor
🎯 Шпаргалка для интервью
Обязательно знать:
- supplyAsync(Supplier
) — асинхронная задача с возвратом результата - runAsync(Runnable) — асинхронная задача без возврата (Void)
- Без Executor: ForkJoinPool.commonPool(), с Executor: указанный пул
- Для I/O всегда передавайте свой Executor
- supplyAsync для вычислений и fetch-операций, runAsync для side effects
Частые уточняющие вопросы:
- supplyAsync vs runAsync — когда что? — supplyAsync когда нужен результат (Supplier
), runAsync когда нет (Runnable) - supplyAsync без Executor — какой пул? — ForkJoinPool.commonPool(), для I/O это thread pool starvation
- Exception в supplier — что будет? — CF завершится с CompletionException, нужно exceptionally/handle
- supplyAsync vs thenApplyAsync? — supplyAsync создаёт новый CF, thenApplyAsync трансформирует существующий
Красные флаги (НЕ говорить):
- «supplyAsync и runAsync это одно и то же» — supplyAsync возвращает T, runAsync возвращает Void
- «supplyAsync без Executor безопасен для HTTP» — blocking I/O в commonPool → starvation
- «supplyAsync создаёт новый поток» — использует ForkJoinPool.commonPool() или указанный Executor
Связанные темы:
- [[12. Какой пул потоков используется по умолчанию для async методов]]
- [[13. Как указать свой Executor для CompletableFuture]]
- [[5. Что делают методы thenAccept() и thenRun()]]
- [[15. Почему важно избегать блокирующих операций в CompletableFuture]]