← Назад к вопросам
В чем разница между Future, FutureTask и CompletableFuture?
2.2 Middle🔥 171 комментариев
#Многопоточность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Разница между Future, FutureTask и CompletableFuture
Это вопрос, который показывает понимание асинхронного программирования в Java. Расскажу о различиях и когда использовать каждый.
Future - базовый интерфейс
Future - это интерфейс, который представляет результат асинхронного вычисления, который может быть недоступен в данный момент:
public interface Future<V> {
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
}
Пример использования Future:
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<Integer> future = executor.submit(() -> {
Thread.sleep(1000);
return 42;
});
// Блокирующий вызов - ждёт результата
Integer result = future.get();
System.out.println("Result: " + result);
// С таймаутом
Integer result2 = future.get(2, TimeUnit.SECONDS);
Проблемы Future:
// Невозможно объединить два Future'a
Future<Integer> future1 = executor.submit(() -> 10);
Future<Integer> future2 = executor.submit(() -> 20);
// Как получить сумму? Нужно писать verbose код
Integer sum = future1.get() + future2.get(); // Блокирует
// Нельзя обработать исключение
Future<String> futureStr = executor.submit(() -> {
throw new IOException("Failed");
});
// futureStr.get() выбросит ExecutionException
FutureTask - реализация Future
FutureTask - это класс, который реализует Future и Runnable. Это мост между Future и Runnable:
public class FutureTask<V> extends Object
implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
Примеры использования FutureTask:
// Способ 1: прямое использование
FutureTask<Integer> task = new FutureTask<>(() -> {
System.out.println("Computing...");
return 42;
});
// Выполнить в отдельном потоке
new Thread(task).start();
// Получить результат
Integer result = task.get(); // Блокирует если ещё не готово
// Способ 2: с ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(1);
FutureTask<String> task2 = new FutureTask<>(() -> "Hello");
executor.execute(task2);
String result2 = task2.get();
Основные отличия FutureTask от Future:
// FutureTask можно передать как Runnable
Runnable runnable = new FutureTask<>(() -> 42);
new Thread(runnable).start();
// FutureTask можно отмотать назад (reset не предусмотрен, но можно создать новый)
FutureTask<Integer> task = new FutureTask<>(() -> 42);
task.run(); // Выполняется как Runnable
Integer result = task.get(); // Получить результат
// task.run() ещё раз - выполнится заново (в отличие от стандартного Future)
Практический пример FutureTask:
public class DataFetcher {
private final Map<String, FutureTask<Data>> cache = new ConcurrentHashMap<>();
public Data getData(String key) {
FutureTask<Data> task = cache.computeIfAbsent(key, k ->
new FutureTask<>(() -> fetchFromDatabase(k))
);
if (!task.isDone()) {
new Thread(task).start();
}
try {
return task.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
}
CompletableFuture - современный подход
CompletableFuture (Java 8+) решает все проблемы Future. Это не просто результат, это цепочка асинхронных операций:
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("Computing...");
return 42;
});
// Неблокирующая обработка результата
future.thenAccept(result -> System.out.println("Result: " + result));
// Не ждём - программа может продолжать работать
Оъединение нескольких CompletableFuture:
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 20);
// Объединение - неблокирующее
CompletableFuture<Integer> combined = future1.thenCombine(future2, (a, b) -> a + b);
combined.thenAccept(sum -> System.out.println("Sum: " + sum));
Обработка исключений:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("Error!");
});
// Обработка ошибки
future.exceptionally(ex -> {
System.err.println("Error occurred: " + ex.getMessage());
return "Default value";
});
// Или
future.handle((result, ex) -> {
if (ex != null) {
return "Error: " + ex.getMessage();
}
return "Success: " + result;
}).thenAccept(System.out::println);
Цепочки преобразований:
CompletableFuture.supplyAsync(() -> fetchUser(1))
.thenApply(user -> user.getOrders())
.thenApply(orders -> orders.stream().mapToDouble(Order::getAmount).sum())
.thenAccept(total -> System.out.println("Total spent: " + total))
.exceptionally(ex -> {
ex.printStackTrace();
return null;
});
Таблица сравнения
Future FutureTask CompletableFuture
─────────────────────────────────────────────────────────────────
Тип Интерфейс Класс Класс
Blocking Да Да Нет (но возможно)
Составление Нет Нет Да (thenCompose)
Объединение Нет Нет Да (thenCombine)
Обработка Нет Нет Да (handle,
ошибок exceptionally)
Transformация Нет Нет Да (thenApply,
thenMap)
Ленивое Нет Нет Да (thenApplyAsync)
выполнение
Полностью Нет Нет Да (можно вручную
контроль (только завершить через
результата submit) complete/
completeExceptionally)
Когда использовать каждый
Future
// Используй для простых случаев с ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(4);
Future<String> future = executor.submit(() -> "result");
String result = future.get(); // Блокирующий вызов OK здесь
FutureTask
// Используй когда нужен контроль над Runnable
FutureTask<Data> task = new FutureTask<>(() -> expensiveComputation());
// Выполнить в thread pool
executor.execute(task);
// Или в отдельном потоке
new Thread(task).start();
Data result = task.get();
CompletableFuture
// Используй для асинхронной обработки в современном коде
CompletableFuture.supplyAsync(() -> fetchUser())
.thenApply(user -> user.getOrders())
.thenApply(orders -> calculateTotal(orders))
.thenAccept(total -> sendNotification(total))
.exceptionally(ex -> {
log.error("Failed", ex);
return null;
});
Практический пример: обработка нескольких запросов
// С Future - много boilerplate
ExecutorService executor = Executors.newFixedThreadPool(3);
Future<User> userFuture = executor.submit(() -> api.getUser(1));
Future<List<Post>> postsFuture = executor.submit(() -> api.getPosts(1));
User user = userFuture.get();
List<Post> posts = postsFuture.get();
UserProfileDTO dto = new UserProfileDTO(user, posts);
// С CompletableFuture - элегантно
CompletableFuture.supplyAsync(() -> api.getUser(1))
.thenCombine(
CompletableFuture.supplyAsync(() -> api.getPosts(1)),
UserProfileDTO::new
)
.thenAccept(dto -> response.send(dto))
.exceptionally(ex -> {
response.error(ex);
return null;
});
Выводы
- Future - используй редко, только для легаси кода
- FutureTask - используй когда нужно контролировать Runnable/Future
- CompletableFuture - используй в современном коде для:
- Асинхронной обработки
- Объединения нескольких операций
- Обработки ошибок
- Неблокирующего программирования
В новых проектах предпочитай CompletableFuture. Это мощный инструмент для написания реактивного, неблокирующего кода.