Asynchronous processing is a powerful technique for improving the performance and responsiveness of Java applications. CompletableFuture, introduced in Java 8, provides a flexible and powerful way to handle asynchronous programming. Below is a comprehensive guide to using CompletableFuture
for asynchronous processing in Java.
Key Features of CompletableFuture
- Asynchronous Execution: Execute tasks asynchronously.
- Chaining: Chain multiple asynchronous tasks.
- Combining: Combine results from multiple asynchronous tasks.
- Exception Handling: Handle exceptions in asynchronous tasks.
- Completion Actions: Execute actions when a task completes.
Basic Usage of CompletableFuture
1. Creating a CompletableFuture
- Run a task asynchronously:
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println("Running task asynchronously");
});
- Supply a result asynchronously:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
return "Result of the asynchronous computation";
});
2. Chaining Tasks
- thenApply: Apply a function to the result of a CompletableFuture.
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(result -> result + " World");
- thenAccept: Consume the result of a CompletableFuture.
CompletableFuture.supplyAsync(() -> "Hello")
.thenAccept(result -> System.out.println(result + " World"));
- thenRun: Run a task after the CompletableFuture completes.
CompletableFuture.supplyAsync(() -> "Hello")
.thenRun(() -> System.out.println("Task completed"));
3. Combining CompletableFutures
- thenCombine: Combine the results of two CompletableFutures.
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> " World");
CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (result1, result2) -> result1 + result2);
- allOf: Wait for all CompletableFutures to complete.
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> " World");
CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2);
allFutures.thenRun(() -> {
System.out.println(future1.join() + future2.join());
});
4. Exception Handling
- exceptionally: Handle exceptions by providing a default value.
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("Error occurred");
}).exceptionally(ex -> "Default Value");
- handle: Handle both results and exceptions.
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("Error occurred");
}).handle((result, ex) -> {
if (ex != null) {
return "Default Value";
}
return result;
});
5. Completion Actions
- whenComplete: Execute an action when the CompletableFuture completes, regardless of success or failure.
CompletableFuture.supplyAsync(() -> "Hello")
.whenComplete((result, ex) -> {
if (ex != null) {
System.out.println("Exception occurred: " + ex.getMessage());
} else {
System.out.println("Result: " + result);
}
});
Advanced Usage of CompletableFuture
1. Composing CompletableFutures
- thenCompose: Compose two CompletableFutures sequentially.
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
.thenCompose(result -> CompletableFuture.supplyAsync(() -> result + " World"));
2. Timeout Handling
- orTimeout: Complete the CompletableFuture exceptionally if it does not complete within the specified timeout.
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello";
}).orTimeout(1, TimeUnit.SECONDS);
3. Cancellation
- cancel: Cancel the CompletableFuture.
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello";
});
future.cancel(true);
Best Practices
- Avoid Blocking: Use non-blocking methods like
thenApply
,thenAccept
, andthenRun
. - Handle Exceptions: Always handle exceptions to avoid unexpected failures.
- Use Thread Pools: Customize the thread pool for better control over resource usage.
ExecutorService executor = Executors.newFixedThreadPool(10);
CompletableFuture.supplyAsync(() -> "Hello", executor);
- Combine and Compose: Use
thenCombine
andthenCompose
to manage complex workflows. - Monitor and Log: Monitor and log the progress and results of asynchronous tasks.
Resources
- Official Documentation: CompletableFuture
- Tutorials and Examples: CompletableFuture Tutorial