Как реализовать timeout для CompletableFuture
В Java 9+ есть встроенный метод orTimeout():
🟢 Junior Level
В Java 9+ есть встроенный метод orTimeout():
CompletableFuture<String> cf = fetchDataAsync();
// Таймаут 5 секунд
cf.orTimeout(5, TimeUnit.SECONDS)
.thenAccept(result -> System.out.println(result))
.exceptionally(ex -> {
if (ex instanceof TimeoutException) {
System.out.println("Timeout!");
}
return "fallback";
});
Для Java 8 — можно реализовать вручную:
public static <T> CompletableFuture<T> withTimeout(
CompletableFuture<T> cf, long timeout, TimeUnit unit
) {
CompletableFuture<T> timeoutCF = new CompletableFuture<>();
// ⚠️ Внимание: этот пример создаёт новый ScheduledThreadPool на каждый вызов.
// В production используйте один общий scheduler или не забудьте scheduler.shutdown().
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.schedule(() ->
timeoutCF.completeExceptionally(new TimeoutException()),
timeout, unit
);
return CompletableFuture.anyOf(cf, timeoutCF)
.thenApply(result -> (T) result);
}
🟡 Middle Level
Java 9+ methods
orTimeout:
cf.orTimeout(5, TimeUnit.SECONDS)
.exceptionally(ex -> {
// TimeoutException
return fallback;
});
completeOnTimeout:
// При таймауте — fallback значение
cf.completeOnTimeout("default", 5, TimeUnit.SECONDS)
.thenAccept(result -> System.out.println(result));
Java 8 implementation
public static <T> CompletableFuture<T> withTimeout(
CompletableFuture<T> cf, long timeout, TimeUnit unit
) {
CompletableFuture<T> result = new CompletableFuture<>();
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
// Schedule timeout
ScheduledFuture<?> timeoutFuture = scheduler.schedule(() -> {
result.completeExceptionally(new TimeoutException());
}, timeout, unit);
// Complete normally
cf.whenComplete((value, ex) -> {
timeoutFuture.cancel(false);
if (ex != null) {
result.completeExceptionally(ex);
} else {
result.complete(value);
}
});
return result;
}
Типичные ошибки
- Не закрыть scheduler: ```java // ❌ Scheduler не закрыт — memory leak ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
// ✅ Закрыть после использования scheduler.shutdown();
---
## 🔴 Senior Level
### Internal Implementation
**orTimeout internals:**
```java
// orTimeout регистрирует whenComplete обработчик, который при нормальном
// завершении вызывает scheduledFuture.cancel(false), отменяя отложенную задачу.
// Если таймаут срабатывает первым — completeExceptionally(TimeoutException)
// завершает CF, и цепочка коллбэков больше не выполняется.
Edge Cases
1. Timeout vs cancellation:
cf.orTimeout(5, TimeUnit.SECONDS);
// Timeout НЕ отменяет выполнение задачи!
// Задача продолжает выполняться в фоне
// Просто CF завершается с TimeoutException
2. Multiple timeouts:
// Последний timeout побеждает
cf.orTimeout(10, TimeUnit.SECONDS)
.orTimeout(5, TimeUnit.SECONDS); // этот сработает первым
Production Experience
Resilient service:
@Service
public class ResilientService {
public CompletableFuture<Data> getData(String key) {
return fetchDataAsync(key)
.orTimeout(5, TimeUnit.SECONDS)
.exceptionallyCompose(ex -> {
if (ex instanceof TimeoutException) {
return getFromCacheAsync(key);
}
return CompletableFuture.failedFuture(ex);
});
}
}
Best Practices
// ✅ orTimeout для Java 9+
cf.orTimeout(5, TimeUnit.SECONDS);
// ✅ completeOnTimeout для fallback
cf.completeOnTimeout(defaultValue, 5, TimeUnit.SECONDS);
// ✅ Custom timeout для Java 8
withTimeout(cf, 5, TimeUnit.SECONDS);
// ❌ Забывать закрыть scheduler
// ❌ Игнорирование TimeoutException
🎯 Шпаргалка для интервью
Обязательно знать:
- orTimeout(timeout, unit) — Java 9+, завершает CF с TimeoutException
- completeOnTimeout(value, timeout, unit) — Java 9+, fallback значение при таймауте
- Таймаут НЕ отменяет задачу — она продолжает выполняться в фоне
- Java 8: ScheduledExecutorService + anyOf(cf, timeoutCF)
- Один общий scheduler для всех таймаутов, не новый на каждый вызов
Частые уточняющие вопросы:
- orTimeout отменяет задачу? — Нет, CF завершается с TimeoutException, задача продолжает работать
- completeOnTimeout vs orTimeout? — completeOnTimeout возвращает fallback, orTimeout бросает исключение
- Java 8 таймаут без ScheduledExecutor? — Нет альтернативы. ScheduledExecutorService — единственный вариант
- Multiple timeouts на один CF? — Последний (самый короткий) побеждает
Красные флаги (НЕ говорить):
- «orTimeout отменяет выполнение задачи» — задача продолжает работать
- «Создаю новый ScheduledThreadPool на каждый timeout» — memory leak, нужен один общий
- «TimeoutException не нужно обрабатывать» — нужно fallback или retry
Связанные темы:
- [[21. Что делает метод orTimeout() в Java 9+]]
- [[10. Что делает метод anyOf() и в каких случаях он полезен]]
- [[27. Как реализовать retry логику с помощью CompletableFuture]]
- [[16. Как правильно выполнить несколько параллельных запросов к микросервисам]]