Що робить метод 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]]