Concurrency Enhancements in Java 8
CompletableFuture
Java 8 introduced the CompletableFuture
class as part of the java.util.concurrent
package. It represents a future result of an asynchronous computation and provides a variety of methods to create, combine, and process asynchronous tasks in a functional style.
Creating a CompletableFuture
You can create a CompletableFuture
using the CompletableFuture
class's static factory methods.
Example: Creating a CompletableFuture
import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class CompletableFutureCreationExample { public static void main(String[] args) throws ExecutionException, InterruptedException { // Creating an already completed CompletableFuture CompletableFuture completedFuture = CompletableFuture.completedFuture("Hello, World!"); System.out.println("Completed Future: " + completedFuture.get()); // Creating a CompletableFuture that runs asynchronously CompletableFuture asyncFuture = CompletableFuture.runAsync(() -> { System.out.println("Running asynchronously"); }); // Waiting for the asyncFuture to complete asyncFuture.get(); } }
Chaining CompletableFuture
CompletableFuture
allows you to chain multiple tasks together using methods like thenApply
, thenAccept
, and thenRun
.
Example: Chaining CompletableFuture
import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class CompletableFutureChainingExample { public static void main(String[] args) throws ExecutionException, InterruptedException { CompletableFuture future = CompletableFuture.supplyAsync(() -> "Hello") .thenApply(greeting -> greeting + ", World!") .thenApply(message -> message + " Welcome to CompletableFuture."); System.out.println("Result: " + future.get()); } }
Combining CompletableFutures
Multiple CompletableFuture
instances can be combined using methods like thenCombine
and thenCompose
.
Example: Combining CompletableFutures
import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class CompletableFutureCombiningExample { public static void main(String[] args) throws ExecutionException, InterruptedException { CompletableFuture future1 = CompletableFuture.supplyAsync(() -> "Hello"); CompletableFuture future2 = CompletableFuture.supplyAsync(() -> "World"); CompletableFuture combinedFuture = future1.thenCombine(future2, (f1, f2) -> f1 + ", " + f2); System.out.println("Combined Result: " + combinedFuture.get()); } }
Handling Errors
CompletableFuture
provides methods like exceptionally
and handle
to handle errors that may occur during the asynchronous computation.
Example: Handling Errors
import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class CompletableFutureErrorHandlingExample { public static void main(String[] args) throws ExecutionException, InterruptedException { CompletableFuture future = CompletableFuture.supplyAsync(() -> { if (true) { throw new RuntimeException("Exception occurred!"); } return "Hello, World!"; }).exceptionally(ex -> "Error: " + ex.getMessage()); System.out.println("Result: " + future.get()); } }
Combining Results from Multiple CompletableFutures
You can combine the results of multiple CompletableFuture
instances using methods like allOf
and anyOf
.
Example: Combining Results from Multiple CompletableFutures
import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.List; import java.util.stream.Collectors; public class CompletableFutureAllOfExample { public static void main(String[] args) throws ExecutionException, InterruptedException { List > futures = List.of( CompletableFuture.supplyAsync(() -> "Task 1"), CompletableFuture.supplyAsync(() -> "Task 2"), CompletableFuture.supplyAsync(() -> "Task 3") ); CompletableFuture allOfFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); CompletableFuture > allResults = allOfFuture.thenApply(v -> futures.stream() .map(CompletableFuture::join) .collect(Collectors.toList()) ); allResults.get().forEach(System.out::println); } }
Using CompletableFuture with Executors
You can provide an Executor
to CompletableFuture
to control the threading behavior.
Example: Using CompletableFuture with Executors
import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; public class CompletableFutureWithExecutorsExample { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(2); CompletableFuture future = CompletableFuture.supplyAsync(() -> { return "Hello, World!"; }, executor); System.out.println("Result: " + future.get()); executor.shutdown(); } }
Conclusion
The CompletableFuture
class in Java 8 provides a powerful framework for building and combining asynchronous tasks. It offers a rich set of methods for task chaining, combining results, handling errors, and working with custom executors, making it an essential tool for modern concurrent programming in Java.