Java Asynchronous Processing with CompletableFuture

Loading

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

  1. Asynchronous Execution: Execute tasks asynchronously.
  2. Chaining: Chain multiple asynchronous tasks.
  3. Combining: Combine results from multiple asynchronous tasks.
  4. Exception Handling: Handle exceptions in asynchronous tasks.
  5. 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

  1. Avoid Blocking: Use non-blocking methods like thenApply, thenAccept, and thenRun.
  2. Handle Exceptions: Always handle exceptions to avoid unexpected failures.
  3. Use Thread Pools: Customize the thread pool for better control over resource usage.
  ExecutorService executor = Executors.newFixedThreadPool(10);
  CompletableFuture.supplyAsync(() -> "Hello", executor);
  1. Combine and Compose: Use thenCombine and thenCompose to manage complex workflows.
  2. Monitor and Log: Monitor and log the progress and results of asynchronous tasks.

Resources


Leave a Reply

Your email address will not be published. Required fields are marked *