Как отменить выполнение CompletableFuture
CompletableFuture можно отменить методом cancel(), но есть нюансы:
🟢 Junior Level
CompletableFuture можно отменить методом cancel(), но есть нюансы:
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
Thread.sleep(5000);
return "Done";
});
// Отмена
boolean cancelled = cf.cancel(true); // true = mayInterruptIfRunning
System.out.println(cf.isCancelled()); // true
Важно: cancel() не всегда останавливает выполнение! Он только устанавливает статус cancelled.
🟡 Middle Level
Как работает cancel
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
// Эта задача продолжит выполняться даже после cancel!
Thread.sleep(5000);
return "Done";
});
cf.cancel(false); // не прерывает
// Задача всё ещё выполняется в фоне!
Почему: cancel() устанавливает результат в CancellationException, но не прерывает поток.
Правильная отмена
1. Cooperative cancellation:
AtomicBoolean cancelled = new AtomicBoolean(false);
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
for (int i = 0; i < 1000; i++) {
if (cancelled.get()) {
throw new CancellationException("Task cancelled");
}
// do work
}
return "Done";
});
// Отмена
cancelled.set(true);
cf.cancel(false);
2. Timeout:
cf.orTimeout(5, TimeUnit.SECONDS); // автоотмена через 5 секунд
Типичные ошибки
- Ожидание прерывания: ```java // ❌ cancel() не прерывает поток cf.cancel(true); // задача продолжает выполняться
// ✅ Cooperative cancellation if (Thread.currentThread().isInterrupted()) { throw new CancellationException(); }
---
## 🔴 Senior Level
### Internal Implementation
```java
// В отличие от FutureTask, CompletableFuture.cancel(true) пытается
// прервать поток через Thread.interrupt(). Но это работает только
// если задача проверяет Thread.interrupted() или выполняет блокирующую
// операцию, реагирующую на interrupt.
// На практике cooperative cancellation (через volatile flag) — основной подход.
Архитектурные Trade-offs
| Подход | Плюсы | Минусы |
|---|---|---|
| cancel() | Просто | Не прерывает |
| Cooperative | Надёжно | Нужно писать код |
| Timeout | Авто | Только время |
Edge Cases
1. Dependent CFs:
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> cf2 = cf1.thenApply(s -> s + " World");
cf1.cancel(false);
// cf2 тоже будет отменён — зависит от cf1
2. Interrupt flag:
// Результат зависит от тайминга. Если задача уже завершилась — cancel()
// вернёт false и isCancelled() будет false. Если задача ещё выполняется
// и реагирует на interrupt — будет true. Thread.sleep() реагирует на interrupt.
// Thread.interrupted() может помочь
CompletableFuture.supplyAsync(() -> {
while (!Thread.currentThread().isInterrupted()) {
// work
}
throw new CancellationException();
});
Production Experience
public class CancellableTask {
private volatile boolean cancelled = false;
private CompletableFuture<String> future;
public CompletableFuture<String> start() {
future = CompletableFuture.supplyAsync(() -> {
while (!cancelled) {
// work
}
if (cancelled) {
throw new CancellationException();
}
return "Done";
});
return future;
}
public void cancel() {
cancelled = true;
if (future != null) {
future.cancel(false);
}
}
}
Best Practices
// ✅ Cooperative cancellation
AtomicBoolean cancelled = new AtomicBoolean(false);
// ✅ Timeout
cf.orTimeout(5, TimeUnit.SECONDS);
// ✅ Проверка interrupt flag
if (Thread.currentThread().isInterrupted()) throw new CancellationException();
// ❌ Ожидание что cancel() прервёт поток
// ❌ Игнорирование CancellationException
🎯 Шпаргалка для интервью
Обязательно знать:
- cancel(true) пытается прервать поток через Thread.interrupt(), но на практике не работает
- Cooperative cancellation (volatile flag / AtomicBoolean) — основной подход
- orTimeout() — автоматическая отмена по времени
- При отмене корневого CF все зависимые завершатся с CancellationException
- Отмена дочерней цепочки НЕ влияет на корневой CF
Частые уточняющие вопросы:
- cancel(true) прервёт выполнение? — Нет, только устанавливает статус. Задача продолжает работать
- Как правильно отменить? — Cooperative: AtomicBoolean flag + проверка в цикле + cancel(false)
- Что с зависимыми CF при отмене? — Завершаются с CancellationException
- obtrudeValue для отмены? — Нет, obtrudeValue принудительно меняет результат — dangerous, race conditions
Красные флаги (НЕ говорить):
- «cancel(true) гарантированно останавливает задачу» — только interrupt flag, задача может игнорировать
- «После cancel() задача сразу завершается» — продолжает выполняться, нужно cooperative cancellation
- «obtrudeValue — нормальный способ отмены» — нарушает контракт однократного завершения, race conditions
Связанные темы:
- [[20. Как реализовать timeout для CompletableFuture]]
- [[26. Можно ли вручную завершить CompletableFuture результатом]]
- [[19. Можно ли повторно использовать один CompletableFuture в нескольких цепочках]]
- [[18. Что произойдёт, если в цепочке CompletableFuture возникнет исключение]]