Асинхронное программирование уже давно является полноценной частью Java. С появлением Java 8 и введением класса CompletableFuture, асинхронное программирование стало более доступным.
CompletableFuture — это класс в пакете java.util.concurrent, предоставляющий возможности для асинхронного программирования. Он поддерживает выполнение задач в фоновом режиме, цепочки задач, обработку исключений и многое другое.
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// Ввыполнение задачи
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Результат";
});
future.thenAccept(result -> {
// обработка результата
System.out.println("Получен результат: " + result);
});
thenApply() используется для обработки результата CompletableFuture и возвращает новый CompletableFuture с преобразованным результатом:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello, World!");
CompletableFuture<String> transformedFuture = future.thenApply(result -> result.toUpperCase());
transformedFuture.thenAccept(result -> {
System.out.println("Преобразованный результат: " + result);
});
thenAccept() принимает результат CompletableFuture и выполняет действие, не возвращая нового значения. Хорош для случаев, когда нужно просто обработать результат:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello, World!");
future.thenAccept(result -> {
System.out.println("Результат: " + result);
});
thenRun() выполняет указанное действие после завершения CompletableFuture, не используя его результат. Это хорошо подходит для логирования или освобождения ресурсов.
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello, World!");
future.thenRun(() -> {
System.out.println("Задача завершена!");
});
thenCompose() используется для объединения двух зависимых задач. Когда первая задача завершится, thenCompose() используется для запуска следующей задачи, используя результат первой. Это предотвращает некоторую вложенность.
Например, получение информации о юзере и его кредит рейтинга:
CompletableFuture<User> getUserDetail(String userId) {
return CompletableFuture.supplyAsync(() -> {
// запрос к удаленному сервису для получения данных пользователя
return UserService.getUserDetails(userId);
});
}
CompletableFuture<Double> getCreditRating(User user) {
return CompletableFuture.supplyAsync(() -> {
// запрос к другому сервису для получения кредитного рейтинга
return CreditRatingService.getCreditRating(user);
});
}
CompletableFuture<Double> result = getUserDetail("123")
.thenCompose(user -> getCreditRating(user));
result.thenAccept(rating -> {
System.out.println("Кредитный рейтинг: " + rating);
});
thenCompose() используется для последовательного выполнения задач: сначала получение данных пользователя, затем — кредитного рейтинга на основе полученных данных пользователя.
thenCombine() используется для объединения результатов двух независимых задач. Он запускает обе задачи параллельно и комбинирует их результаты, когда обе задачи завершены.
Например, расчет BMI:
CompletableFuture<Double> weightInKgFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return 65.0;
});
CompletableFuture<Double> heightInCmFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return 175.0;
});
CompletableFuture<Double> bmiFuture = weightInKgFuture.thenCombine(heightInCmFuture, (weight, height) -> {
double heightInMeters = height / 100;
return weight / (heightInMeters * heightInMeters);
});
bmiFuture.thenAccept(bmi -> {
System.out.println("Индекс массы тела: " + bmi);
});
thenCombine() объединяет результаты двух асинхронных задач: получение веса и роста, чтобы рассчитать BMI.
Паттерн Fan-Out/Fan-In позволяет выполнять несколько задач параллельно и затем объединять их результаты. Это можно реализовать с помощью методов allOf() и anyOf().
allOf() позволяет запускать несколько задач параллельно и ждать их завершения:
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
// задача 1
System.out.println("Задача 1 выполнена");
});
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
// задача 2
System.out.println("Задача 2 выполнена");
});
CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(future1, future2);
combinedFuture.thenRun(() -> {
System.out.println("Все задачи выполнены");
});
В этом примере обе задачи запускаются параллельно, и thenRun() выполняется после их завершения.
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (new Random().nextBoolean()) {
throw new RuntimeException("Что-то пошло не так");
}
return "Успех!";
}).exceptionally(ex -> {
System.out.println("Обработка исключения: " + ex.getMessage());
return "Восстановлено после ошибки";
});
future.thenAccept(result -> System.out.println("Результат: " + result));
Если задача завершится с ошибкой, exceptionally() обработает исключение и вернет строку "Восстановлено после ошибки".
Метод handle() позволяет обработать как успешный результат, так и исключение, используя BiFunction, принимающий результат или исключение в качестве аргументов. Он всегда выполняется независимо от того, была ли ошибка:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (new Random().nextBoolean()) {
throw new RuntimeException("Что-то пошло не так");
}
return "Успех!";
}).handle((result, ex) -> {
if (ex != null) {
System.out.println("Обработка исключения: " + ex.getMessage());
return "Восстановлено после ошибки";
}
return result;
});
future.thenAccept(result -> System.out.println("Результат: " + result));
handle() обработает как результат, так и исключение, возвращая соответствующее значение в зависимости от исхода задачи.
Метод completeOnTimeout() позволяет установить значение, которое будет возвращено, если задача не завершится в течение указанного времени:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Успех!";
}).completeOnTimeout("Тайм-аут", 1, TimeUnit.SECONDS);
future.thenAccept(result -> System.out.println("Результат: " + result));
Если задача не завершится в течение 1 секунды, она будет завершена со значением "Тайм-аут".
CompletableFuture — это класс в пакете java.util.concurrent, предоставляющий возможности для асинхронного программирования. Он поддерживает выполнение задач в фоновом режиме, цепочки задач, обработку исключений и многое другое.
Основные методы CompletableFuture
Метод supplyAsync() используется для асинхронного выполнения задачи, возвращающей результат. Задача выполняется в фоновом потоке, предоставляемом ForkJoinPool.commonPool(), если не указан другой Executor:CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// Ввыполнение задачи
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Результат";
});
future.thenAccept(result -> {
// обработка результата
System.out.println("Получен результат: " + result);
});
thenApply() используется для обработки результата CompletableFuture и возвращает новый CompletableFuture с преобразованным результатом:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello, World!");
CompletableFuture<String> transformedFuture = future.thenApply(result -> result.toUpperCase());
transformedFuture.thenAccept(result -> {
System.out.println("Преобразованный результат: " + result);
});
thenAccept() принимает результат CompletableFuture и выполняет действие, не возвращая нового значения. Хорош для случаев, когда нужно просто обработать результат:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello, World!");
future.thenAccept(result -> {
System.out.println("Результат: " + result);
});
thenRun() выполняет указанное действие после завершения CompletableFuture, не используя его результат. Это хорошо подходит для логирования или освобождения ресурсов.
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello, World!");
future.thenRun(() -> {
System.out.println("Задача завершена!");
});
Комбинирование и цепочки
Можно объединять и связывать различные задачи.thenCompose() используется для объединения двух зависимых задач. Когда первая задача завершится, thenCompose() используется для запуска следующей задачи, используя результат первой. Это предотвращает некоторую вложенность.
Например, получение информации о юзере и его кредит рейтинга:
CompletableFuture<User> getUserDetail(String userId) {
return CompletableFuture.supplyAsync(() -> {
// запрос к удаленному сервису для получения данных пользователя
return UserService.getUserDetails(userId);
});
}
CompletableFuture<Double> getCreditRating(User user) {
return CompletableFuture.supplyAsync(() -> {
// запрос к другому сервису для получения кредитного рейтинга
return CreditRatingService.getCreditRating(user);
});
}
CompletableFuture<Double> result = getUserDetail("123")
.thenCompose(user -> getCreditRating(user));
result.thenAccept(rating -> {
System.out.println("Кредитный рейтинг: " + rating);
});
thenCompose() используется для последовательного выполнения задач: сначала получение данных пользователя, затем — кредитного рейтинга на основе полученных данных пользователя.
thenCombine() используется для объединения результатов двух независимых задач. Он запускает обе задачи параллельно и комбинирует их результаты, когда обе задачи завершены.
Например, расчет BMI:
CompletableFuture<Double> weightInKgFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return 65.0;
});
CompletableFuture<Double> heightInCmFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return 175.0;
});
CompletableFuture<Double> bmiFuture = weightInKgFuture.thenCombine(heightInCmFuture, (weight, height) -> {
double heightInMeters = height / 100;
return weight / (heightInMeters * heightInMeters);
});
bmiFuture.thenAccept(bmi -> {
System.out.println("Индекс массы тела: " + bmi);
});
thenCombine() объединяет результаты двух асинхронных задач: получение веса и роста, чтобы рассчитать BMI.
Паттерн Fan-Out/Fan-In позволяет выполнять несколько задач параллельно и затем объединять их результаты. Это можно реализовать с помощью методов allOf() и anyOf().
allOf() позволяет запускать несколько задач параллельно и ждать их завершения:
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
// задача 1
System.out.println("Задача 1 выполнена");
});
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
// задача 2
System.out.println("Задача 2 выполнена");
});
CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(future1, future2);
combinedFuture.thenRun(() -> {
System.out.println("Все задачи выполнены");
});
В этом примере обе задачи запускаются параллельно, и thenRun() выполняется после их завершения.
Обработка ошибок и тайм-ауты в CompletableFuture
Метод exceptionally() позволяет обработать исключение и вернуть значение, которое заменит результат завершившегося с ошибкой CompletableFuture. Метод вызывается только в случае исключения и не активируется при успешном завершении задачи:CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (new Random().nextBoolean()) {
throw new RuntimeException("Что-то пошло не так");
}
return "Успех!";
}).exceptionally(ex -> {
System.out.println("Обработка исключения: " + ex.getMessage());
return "Восстановлено после ошибки";
});
future.thenAccept(result -> System.out.println("Результат: " + result));
Если задача завершится с ошибкой, exceptionally() обработает исключение и вернет строку "Восстановлено после ошибки".
Метод handle() позволяет обработать как успешный результат, так и исключение, используя BiFunction, принимающий результат или исключение в качестве аргументов. Он всегда выполняется независимо от того, была ли ошибка:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (new Random().nextBoolean()) {
throw new RuntimeException("Что-то пошло не так");
}
return "Успех!";
}).handle((result, ex) -> {
if (ex != null) {
System.out.println("Обработка исключения: " + ex.getMessage());
return "Восстановлено после ошибки";
}
return result;
});
future.thenAccept(result -> System.out.println("Результат: " + result));
handle() обработает как результат, так и исключение, возвращая соответствующее значение в зависимости от исхода задачи.
Метод completeOnTimeout() позволяет установить значение, которое будет возвращено, если задача не завершится в течение указанного времени:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Успех!";
}).completeOnTimeout("Тайм-аут", 1, TimeUnit.SECONDS);
future.thenAccept(result -> System.out.println("Результат: " + result));
Если задача не завершится в течение 1 секунды, она будет завершена со значением "Тайм-аут".
Кратко про класс CompletableFuture в Java
Привет, Хабр! Асинхронное программирование уже давно является полноценной частью Java. С появлением Java 8 и введением класса CompletableFuture , асинхронное программирование стало более доступным....
habr.com