Conteúdo do Curso
Multithreading in Java
Multithreading in Java
CompletableFuture
There's one last leap left! In this chapter we will look at the main asynchrony class CompletableFuture
.
Let’s use a real-life analogy. You hail a cab using an app (task creation). While you wait, the app can provide updates on the car’s location or the estimated arrival time (result processing). If there are issues such as delays or cancellations, the app will notify you and suggest alternatives (exception handling).
Main Methods
The first question you might ask is how to start a task using CompletableFuture
. To do this, you can use the supplyAsync()
method, which is designed to execute a task that returns the result asynchronously.
Main
// Creating an asynchronous task CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { return "Result"; // Result }); // Processing the result after the task completes future.thenAccept(result -> { // Print the result to the console System.out.println("Result received: " + result); // Result received: });
We also have the thenAccept()
method, which handles the value returned from CompletableFuture
in asynchronous code. It does not return anything; it is useful when you need to accept a response and process it in some way.
Note
In our example, we run the task asynchronously, and
future.thenAccept()
gets the response from the lambda, which we can then use to print to the console.
There's a similar method, thenApply()
, which works like thenAccept()
but gets the result of the asynchronous task and returns a new result.
Main
// Creating an asynchronous task that returns "Hello, World!" CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello, World!"); // Transforming the result to uppercase CompletableFuture<String> transformedFuture = future.thenApply(result -> result.toUpperCase()); // Printing the transformed result to the console transformedFuture.thenAccept(result -> { System.out.println("Transformed result: " + result); // Transformed result: });
What if we don't want to get the result of the asynchronous task but simply want to be notified when it has finished?
For this, we can use thenRun()
, which is executed after the asynchronous task completes.
Main
// Creating an asynchronous task that returns "Hello, World!" CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello, World!"); // Running a task when the previous task completes future.thenRun(() -> { // Print message to the console indicating that the task is complete System.out.println("Task completed!"); // Task completed! });
We can also retrieve the result of an asynchronous task by blocking the current thread until the task is completed. For this purpose, we use the get()
method.
Main
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "Hello World"); try { String result = completableFuture.get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); }
There is also a method join()
, which also pauses the current thread and waits for the asynchronous task to complete. However, the difference between join()
and get()
is that they throw different exceptions.
Main
// Create a `CompletableFuture` that asynchronously executes a task and returns the string "Hello World". CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "Hello World"); // The `join()` method blocks the current thread until the task is completed and returns the result "Hello World". String result = completableFuture.join();
We are not handling the exception here because the join()
method throws an unchecked CompletionException
.
Combining Tasks and Chaining them Together
We can combine tasks in CompletableFuture
using thenCompose()
which will combine 2 dependent tasks.
Main
// Method to get book details from a remote service public CompletableFuture<String> getBookDetails() { return CompletableFuture.supplyAsync(() -> { // Request to a remote service to get book details return "Book Details"; // Placeholder for book details }); } // Method to get author details from a remote service public CompletableFuture<String> getAuthorDetails(String bookDetails) { return CompletableFuture.supplyAsync(() -> { // Request to another service to get author details return bookDetails + " Author"; // Placeholder for author details }); } // Combine two asynchronous tasks: get book details and then get author details CompletableFuture<String> result = getBookDetails() .thenCompose(book -> getAuthorDetails(book)); // Process the result and print the author details to the console result.thenAccept(author -> { // Print the author details to the console System.out.println("Author: " + author); // Author details });
We first retrieve the book data asynchronously using the getBookDetails()
method, and then use the result to carry out the next asynchronous task—fetching the author data through the getAuthorDetails()
method. After both tasks are complete, the result (author information) is displayed on the console.
We can also merge the results of two tasks using the thenCombine()
method. It executes both tasks concurrently and combines their results once both tasks are complete.
Main
// CompletableFuture for adding two numbers CompletableFuture<Double> firstNumberFuture = CompletableFuture.supplyAsync(() -> 50.0); CompletableFuture<Double> secondNumberFuture = CompletableFuture.supplyAsync(() -> 30.0); // Combine the two futures by adding their results CompletableFuture<Double> sumFuture = firstNumberFuture.thenCombine(secondNumberFuture, (first, second) -> first + second); // Print the result of the addition to the console sumFuture.thenAccept(sum -> { System.out.println("Sum: " + sum); // Sum: 80.0 });
This code asynchronously retrieves two numbers, sums them, and prints the result to the console.
We can also wait for all tasks to complete using the allOf()
method, or any one of them using the anyOf()
method.
Main
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> { // Task 1 System.out.println("Task 1 completed"); }); CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> { // Task 2 System.out.println("Task 2 completed"); }); // Combine both futures and wait for both tasks to complete CompletableFuture<Void> combinedFuture1 = CompletableFuture.allOf(future1, future2); // Combine both futures and proceed as soon as any one task completes CompletableFuture<Object> combinedFuture2 = CompletableFuture.anyOf(future1, future2); // Print a message after all tasks are completed combinedFuture1.thenRun(() -> { System.out.println("All tasks completed"); });
How do you Handle Errors and What is a Timeout?
Short Clip From the Video
handle()
: Processes the result or handles any exceptions thrown by theCompletableFuture
;exceptionally()
: Handles exceptions thrown during the execution of theCompletableFuture
;completeOnTimeout()
: Completes theCompletableFuture
with a specified value if it times out before completing.
Summary
CompletableFuture
makes it easy to manage asynchronous tasks and process their results, making it a powerful tool for developing modern applications.
Obrigado pelo seu feedback!