Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
CompletableFuture | Multithreading Best Practices
Multithreading in Java
course content

Conteúdo do Curso

Multithreading in Java

Multithreading in Java

1. Multithreading Basics
2. Synchronized Collections
3. High-level Synchronization Mechanisms
4. Multithreading Best Practices

book
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.

java

Main

copy
12345678910
// 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.

java

Main

copy
12345678910
// 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.

java

Main

copy
12345678
// 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.

java

Main

copy
1234567
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.

java

Main

copy
12345
// 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.

java

Main

copy
12345678910111213141516171819202122232425
// 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.

java

Main

copy
123456789101112
// 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.

java

Main

copy
1234567891011121314151617181920
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 the CompletableFuture;
  • exceptionally(): Handles exceptions thrown during the execution of the CompletableFuture;
  • completeOnTimeout(): Completes the CompletableFuture 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.

Tudo estava claro?

Como podemos melhorá-lo?

Obrigado pelo seu feedback!

Seção 4. Capítulo 6
We're sorry to hear that something went wrong. What happened?
some-alt