В сучасних додатках, особливо при роботі з операціями, пов'язаними з ввід/виводом, такими як запити до бази даних, операції з файлами або HTTP запити, асинхронне виконання завдань може покращити продуктивність, дозволяючи системі виконувати кілька операцій одночасно без блокування. У Java CompletableFuture
є потужним інструментом для написання асинхронного, неблокуючого коду.
У цій статті ми розглянемо кілька прикладів використання CompletableFuture
в різних сценаріях, що дозволяє зробити ваші додатки більш ефективними, використовуючи асинхронне виконання.
1. thenAcceptAsync - Виконання дії після завершення
Метод thenAcceptAsync
використовується, коли ви хочете виконати дію (наприклад, надіслати сповіщення) після завершення асинхронної задачі. Дія буде виконана в окремому потоці.
public static void thenAcceptAsyncExample() {
CompletableFuture userProfileFuture = CompletableFuture.supplyAsync(() -> {
sleep(1000);
return "User: John Doe, Email: [email protected]";
});
userProfileFuture.thenAcceptAsync(userProfile -> {
System.out.println("Sending email notification to: " + userProfile);
});
}
2. thenRun - Виконання дії без використання результату
Іноді вам не потрібен результат асинхронної задачі, але ви все одно хочете виконати дію після її завершення. thenRun
дозволяє виконати задачу після завершення іншої без використання її результату.
public static void thenRunExample() {
CompletableFuture orderDetails = CompletableFuture.supplyAsync(() -> {
sleep(1000);
System.out.println("Fetched order details for a phone.");
return "OrderDetails for Phone";
});
userDetails.thenRun(() -> {
System.out.println("Logging: Order details fetched successfully.");
});
}
3. thenRunAsync - Виконання дії асинхронно
Метод thenRunAsync
працює подібно до thenRun
, але виконує наступну дію в окремому потоці, що може бути корисним для незалежних задач, які можуть виконуватись одночасно.
public static void thenRunAsyncExample() {
CompletableFuture orderDetails = CompletableFuture.supplyAsync(() -> {
sleep(1000);
System.out.println("Fetched order details for a phone.");
return "OrderDetails for Phone";
});
userDetails.thenRunAsync(() -> {
System.out.println("Sending information email to user...");
});
}
4. thenCombineAsync - Комбінування результатів двох Future
thenCombineAsync
корисний, коли вам потрібно об'єднати результати двох незалежних асинхронних задач. Логіка об'єднання виконується асинхронно.
public static void thenCombineAsyncExample() {
CompletableFuture weatherForecast = CompletableFuture.supplyAsync(() -> {
sleep(500);
return "Sunny, 25°C";
});
CompletableFuture userLocation = CompletableFuture.supplyAsync(() -> {
sleep(300);
return "Istanbul";
});
CompletableFuture weatherReport = weatherForecast.thenCombineAsync(userLocation,
(forecast, location) -> "Weather in " + location + ": " + forecast);
weatherReport.thenAccept(System.out::println);
}
5. thenApplyAsync - Перетворення результату асинхронно
Якщо вам потрібно перетворити результат асинхронної задачі, ви можете використовувати thenApplyAsync
. Цей метод застосовує функцію до результату асинхронно і повертає новий CompletableFuture
з перетвореним результатом.
public static void thenApplyAsyncExample() {
CompletableFuture productDetails = CompletableFuture.supplyAsync(() -> {
sleep(2000);
return "Product: Smartphone, Price: $799";
});
CompletableFuture promoMessage = productDetails.thenApplyAsync(details ->
"Limited time offer! " + details + " - Get it now!");
promoMessage.thenAccept(System.out::println);
}
thenCompose - Ланцюжок залежних асинхронних задач
Коли у вас є залежні завдання (друге завдання залежить від результату першого), ви можете використовувати `thenCompose`. Це дозволяє з'єднувати асинхронні задачі в ланцюжок.
public static void thenComposeExample() {
CompletableFuture productInfo = getProductInfo("Phone")
.thenCompose(CompletableFutureExamples::getCustomerReviews);
productInfo.thenAccept(System.out::println);
}
```
7. Обробка виключень: exceptionally та handle
Асинхронні задачі можуть не виконуватись, тому важливо обробляти виключення. Ви можете використовувати exceptionally
для обробки виключень та надання значення за замовчуванням, або використовувати handle
для обробки як результату, так і будь-яких виключень.
public static void exceptionallyCF() {
CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("Product not found!");
})
.exceptionally(ex -> "Error: " + ex.getMessage())
.thenAccept(System.out::println);
}
public static void handleCF() {
CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("Error!");
})
.handle((result, ex) -> ex != null ? "Recovered from: " + ex.getMessage() : result)
.thenAccept(System.out::println);
}
8. thenCombine - Комбінування результатів кількох задач
Ви можете комбінувати результати кількох задач за допомогою thenCombine
. Це особливо корисно, коли вам потрібно зібрати дані з кількох джерел і прийняти рішення на основі комбінованих результатів.
public static void combineCF() {
CompletableFuture ordersFuture = getUserOrdersAsync("12345");
CompletableFuture loyaltyPointsFuture = getLoyaltyPointsAsync("12345");
CompletableFuture discountFuture = getCurrentDiscountAsync("12345");
CompletableFuture combinedFuture = ordersFuture
.thenCombine(loyaltyPointsFuture, (orders, loyaltyPoints) -> {
System.out.println("User Orders: " + orders);
System.out.println("Loyalty Points: " + loyaltyPoints);
return loyaltyPoints;
})
.thenCombine(discountFuture, (loyaltyPoints, discount) -> {
String recommendation = makeRecommendation(loyaltyPoints, discount);
return recommendation;
})
.thenAccept(finalRecommendation -> {
System.out.println("Personalized Recommendation: " + finalRecommendation);
});
combinedFuture.join();
}
9. Використання ExecutorService для синхронного виконання
Для деяких завдань ви можете захотіти заблокувати основний потік до того, як буде отриманий результат. ExecutorService
дає гнучкість для подачі задач, які повертають результат синхронно.
Future.get() блокує потік до завершення обчислень. Це корисно, коли вам потрібно почекати результат синхронно.
public static void executorService() throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
Future future = executorService.submit(() -> {
sleep(5000);
return 100;
});
Integer orderValue = future.get();
System.out.println("Order value: " + orderValue);
executorService.shutdown();
}
10. Використання CompletableFuture.supplyAsync для асинхронного виконання
Якщо ви хочете виконати задачу асинхронно без блокування основного потоку, CompletableFuture.supplyAsync
є ідеальним варіантом для таких сценаріїв.
public static void executorServiceWithCFSupplyAsync() throws ExecutionException, InterruptedException {
CompletableFuture voidCompletableFuture = CompletableFuture.supplyAsync(() -> {
sleep(5000);
return 100;
}).thenAccept(result -> System.out.println("Order value: " + result));
}
Висновок
CompletableFuture
надає потужний інструмент для написання асинхронного, неблокуючого коду в Java.
Використовуючи різні методи, такі як thenAcceptAsync
, thenRun
, thenCombineAsync
та обробка виключень, ви можете значно покращити продуктивність і чуйність ваших додатків.
У цій статті ми розглянули кілька прикладів, які допоможуть вам почати працювати з асинхронним програмуванням у Java, використовуючи CompletableFuture
. Розуміння та застосування цих концепцій може допомогти вам створювати більш масштабовані та ефективні системи.
Перекладено з: Exploring Asynchronous Programming with CompletableFuture in Java