CompletableFuture: лучшие практики Java
Основы supplyAsync
Начало работы с supplyAsync
Представьте, что ваше приложение — это повар на кухне. Если повар выполняет все задачи последовательно — нарезает овощи, варит бульон, жарит мясо, — то процесс приготовления блюда сильно затягивается. Основной поток выполнения программы, как и повар, блокируется на каждой долгой операции, например, на запросе к базе данных или обращении к удаленному сервису.
Здесь на помощь приходит асинхронное программирование. Оно позволяет «делегировать» долгие задачи помощникам, не останавливая основную работу. В Java одним из ключевых инструментов для этого является CompletableFuture. Метод supplyAsync — это наш способ отдать задачу в фоновый режим и при этом ожидать, что в итоге мы получим какой-то результат.
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class SupplyAsyncExample {
public static void main(String[] args) throws Exception {
// Запускаем долгую задачу в фоновом потоке
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
System.out.println("Начинаем долгую операцию в потоке: " + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(2); // Имитация долгой работы
System.out.println("Долгая операция завершена.");
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Результат долгой операции";
});
// Основной поток не ждет и продолжает свою работу
System.out.println("Основной поток продолжает выполняться: " + Thread.currentThread().getName());
// Получаем результат, когда он будет готов. Метод .get() блокирует поток до завершения задачи.
String result = future.get();
System.out.println("Получен результат: " + result);
}
}
В этом примере supplyAsync принимает задачу — в данном случае лямбда-выражение. Эта задача выполняется в отдельном потоке, а основной поток сразу же печатает сообщение «Основной поток продолжает выполняться...». Он не ждет две секунды. Только когда нам действительно нужен результат, мы вызываем .get(), который и дожидается его получения.
Supplier против Runnable
Для асинхронных задач в Java есть два основных «контракта» — функциональных интерфейса: Runnable и Supplier. Понимание их разницы — ключ к правильному использованию CompletableFuture.
Runnable представляет собой задачу, которая просто выполняется и ничего не возвращает. Это как команда «сделай что-то». Вы ее отдали и не ждете отчета. Например, «запиши лог в файл».
С другой стороны, — это задача, которая после выполнения поставляет результат. Это команда «добудь что-то». Вы ожидаете, что задача вернет вам значение, например, данные пользователя из базы или результат сложных вычислений. Именно поэтому метод supplyAsync требует Supplier — его цель «поставить» результат асинхронно.
| Характеристика | Runnable | Supplier |
|---|---|---|
| Назначение | Выполнить действие | Выполнить действие и вернуть результат |
| Сигнатура метода | void run() | T get() |
| Возвращает значение? | Нет | Да |
| Пример использования | Логгирование, отправка уведомлений | Запрос данных из сети, вычисления |
| Метод CompletableFuture | runAsync | supplyAsync |
Под капотом: ForkJoinPool
Куда именно supplyAsync отправляет нашу задачу? По умолчанию используется общий пул потоков, доступный через . Это специальный, оптимизированный для коротких задач пул, который управляет потоками-исполнителями. Его размер обычно равен количеству ядер процессора в системе.
Использование общего пула избавляет нас от необходимости вручную создавать и настраивать потоки для каждой асинхронной операции. Java берет это на себя, эффективно распределяя нагрузку. Это похоже на то, как операционная система управляет процессами: вам не нужно думать, на каком ядре процессора запустить калькулятор, система делает это сама.
Хотя
supplyAsyncпозволяет передать и собственныйExecutor(пул потоков), для большинства общих задач отлично подходитForkJoinPool.commonPool()по умолчанию.
Итак, supplyAsync — это мощный и простой способ запустить задачу в фоновом режиме и получить от нее результат. Он инкапсулирует логику выполнения в Supplier и использует общий пул потоков для эффективной работы, оставляя основной поток вашего приложения свободным и отзывчивым.
В контексте аналогии с поваром на кухне, что представляет собой асинхронное программирование с использованием CompletableFuture?
Какой функциональный интерфейс следует передать в метод supplyAsync, если асинхронная задача должна вернуть результат (например, данные из базы)?