Питання 16 · Розділ 19

Що робить метод supplyAsync() і коли його використовувати

Structured Java interview answer with junior, middle, and senior-level explanation.

Мовні версії: English Russian Ukrainian

🟢 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);

Типові помилки

  1. 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]]