Asynchronous Programming in Java CompletableFuture

1 Introduction

This article is a functional classes and guide CompletableFuture use cases - as Java 8 Concurrency API improvements introduced.

2. Java in asynchronous computation

Asynchronous computation difficult reasoning. Usually we expect any computing as a series of steps. But in the case of an asynchronous computation, often expressed as a callback operation code or dispersed in a deeply nested inside each other. When we need to handle the error which may occur in one step, the situation becomes worse.

Future interface Java 5 is added as a result of an asynchronous computation, but it has no method, or a combination of these calculations error that may arise.

In Java 8, we introduced CompletableFuture class. And Future interfaces together, it also implements CompletionStage interface. This interface defines a contract asynchronous computing step may be combined with other steps.

CompletableFuture is also a building block and a frame, having about 50 different combinations, compatible asynchronous computing step and handle errors.

Such a large API may cause overwhelming, but most of these API is clear and several different use cases.

3. Use CompletableFuture as a simple Future

First, CompletableFuture Future class implements an interface, so you can use it as Future achieve, but with the completion of additional logic.

For example, you can create an instance of the use of no-arg constructor to show some of the results of the Future, which was handed over to the user, and complete method to use to complete at some future time. Consumers can use the get method to prevent the current thread until this offer results.

In the following example, we have created a method CompletableFuture instance, then rotate some calculations in another thread and returns immediately Future.

After completion of the calculation, which is accomplished by Future result to complete the method:

public Future<String> calculateAsync() throws InterruptedException {
    CompletableFuture<String> completableFuture = new CompletableFuture<>();

    Executors.newCachedThreadPool().submit(() -> {
        Thread.sleep(500);
        completableFuture.complete("Hello");
        return null;
    });

    return completableFuture;
}

In order to separate calculations, we used the Executor API "Introduction to the thread pool in Java," the article described, but the creation and completion of CompletableFuture method can be used with any or concurrency API (including the original thread).

Note that this method returns an instance of calculateAsync future.

We just call the method, the receiver Future instance and call its methods when we get ready to clog the results.

Also note, get checked methods throw some exceptions, namely (calculated anomaly occurred during the package) ExecutionException and InterruptedException (indicate the method of execution thread is interrupted exception):

Future<String> completableFuture = calculateAsync();

// ... 

String result = completableFuture.get();
assertEquals("Hello", result);

If you already know the result of the calculation, it can be used with static completedFuture method and indicates that this calculation parameters. Then, get method Future never blocked, but returns this result immediately.

Future<String> completableFuture = CompletableFuture.completedFuture("Hello");

// ...

String result = completableFuture.get();
assertEquals("Hello", result);

As an alternative, you may want to cancel the execution of the Future.

Suppose we did not manage to find the results and decided to completely cancel asynchronous execution. This can be done through the Future cancellation method. This method of receiving the mayInterruptIfRunning Boolean parameter, but in the case of CompletableFuture, it has no effect, because the interrupt process is not used to control CompletableFuture.

This is a modified version of the asynchronous method:

public Future<String> calculateAsyncWithCancellation() throws InterruptedException {
    CompletableFuture<String> completableFuture = new CompletableFuture<>();

    Executors.newCachedThreadPool().submit(() -> {
        Thread.sleep(500);
        completableFuture.cancel(false);
        return null;
    });

    return completableFuture;
}

When we use Future.get () method blocks result, if canceled canceled future, it will throw CancellationException:

Future<String> future = calculateAsyncWithCancellation();
future.get(); // CancellationException

4. CompletableFuture calculation logic package having

The above code allows us to choose any concurrent execution mechanism, but if we want to skip this asynchronous model and simply execute some code?

RunAsync supplyAsync static methods and allows us to create a corresponding instance CompletableFuture from Runnable and Supplier function types.

Both sides can be run and supplier of functional interface, allow benefited by them as an example of the new features of Java 8 lambda expressions.

The Runnable interface is the same old interface thread, it does not allow the return value.

General interface provider interface function with a single method will not have a parameter, and returns a value of parameterized types.

This allows the Supplier instance provided as a lambda expression, which performs the calculation expression and returns the result. this is very simple:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");

// ...

assertEquals("Hello", future.get());

The asynchronous computation processing result

The most common method for processing the calculation result is provided to the function. The thenApply method does exactly that: to accept a function instance, use it to process the results, and returns a future saving function's return value:

CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "Hello");

CompletableFuture<String> future = completableFuture.thenApply(s -> s + " World");

assertEquals("Hello World", future.get());

If you do not return the value chain in the Future, you can use the instance of Consumer interfaces. It accepts a single parameter and returns void.

There is a method used in this embodiment is used in the CompletableFuture - thenAccept Consumer method of receiving the calculation result is passed to it. Last future.get () call returns an instance of type Void.

CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "Hello");

CompletableFuture<Void> future = completableFuture.thenAccept(s -> System.out.println("Computation returned: " + s));

future.get();

Finally, if you need to calculate the value of either do not want to return some value at the end of the chain, then you can pass Runnable lambda to thenRun method. In the following example, after calling future.get () method, we simply print a line in the console:

CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "Hello");

CompletableFuture<Void> future = completableFuture.thenRun(() -> System.out.println("Computation finished."));

future.get();

6.CompletableFuture

Best part is CompletableFuture API can be combined in a series of examples CompletableFuture calculation step.

The result of this link is itself CompletableFuture, allowing further links and combinations. Such methods are ubiquitous in a functional language, often referred to monadic design pattern.

In the following example, we use the method of sequentially linked thenCompose two Futures.

Note that this function returns CompletableFuture Methods instance. The parameters of this function is the result of a previous calculation step. This allows us to use a value of lambda CompletableFuture next:

CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "Hello")
.thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " World"));

assertEquals("Hello World", completableFuture.get());

The method thenCompose basic building blocks together thenApply one yuan pattern. They are closely related to Java 8 in map and flatMap and Optional Stream class method available.

A function of receiving two methods and the results used to calculate, but thenCompose (flatMap) method takes a function that returns another object of the same type. This configuration allows the feature instances of these classes as building blocks in combination.

If you want to execute two separate operations and perform some Futures results of its use and Future thenCombine method takes two parameters Function to process two results:

CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "Hello")
.thenCombine(CompletableFuture.supplyAsync(() -> " World"), (s1, s2) -> s1 + s2));

assertEquals("Hello World", completableFuture.get());

Simpler case, when you want to use the results of two futures, but does not require any result value passed to the Future chain. The thenAcceptBoth method is helpful:

CompletableFuture future = CompletableFuture.supplyAsync(() -> "Hello")
.thenAcceptBoth(CompletableFuture.supplyAsync(() -> " World"),
(s1, s2) -> System.out.println(s1 + s2));

The difference between 7. thenApply () and thenCompose ()

In the previous section, we show an example on thenApply () and thenCompose () of. Both API will help to link different CompletableFuture calls, but use two functions are different.

7.1 thenApply()

This method is used to process the results of a previous call. However, a key point to remember is that the return type combine all calls.

Therefore, when we want to convert the result CompletableFuture call, this method is useful:

CompletableFuture<Integer> finalResult = compute().thenApply(s-> s + 1);

7.2 thenCompose()

The thenCompose () method is similar to thenApply () returns in a new stage of completion. However, before thenCompose () using a phase as a parameter. It will make the results directly to flatten and return to the Future, rather than what we observed in thenApply () nested in the future:

CompletableFuture<Integer> computeAnother(Integer i){
    return CompletableFuture.supplyAsync(() -> 10 + i);
}

CompletableFuture<Integer> finalResult = compute().thenCompose(this::computeAnother);

So, if you want to link CompletableFuture method, it is best to use thenCompose ().

Also note that the difference between these two methods is similar to the difference between the map () and flatMap ().

8. A plurality of parallel running Futures

When we need to perform a plurality of parallel Futures, we want to wait for all they perform normally, then the processing result combination thereof.

The CompletableFuture.allOf static method allows to wait for the completion of all futures as a variant - arginine provides:

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Beautiful");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "World");

CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(future1, future2, future3);

// ...

combinedFuture.get();

assertTrue(future1.isDone());
assertTrue(future2.isDone());
assertTrue(future3.isDone());

Please note, CompletableFuture.allOf () return type is CompletableFuture. The limitation of this method is that it does not return the combined result of all futures. Instead, you must manually obtain results from Futures. Fortunately, CompletableFuture.join () method and Java 8 Streams API make it simple:

String combined = Stream.of(future1, future2, future3).map(CompletableFuture::join).collect(Collectors.joining(" "));

assertEquals("Hello Beautiful World", combined);

The CompletableFuture.join () method is similar to the GET method, but it throws an abnormality in an unchecked, does not normally complete in the future. This method makes it possible to be used as reference in Stream.map () method.

9. Handling errors

For calculation step chain asynchronous error handling must be adjusted in a similar manner throw / catch usage.

CompletableFuture class allows you to handle it in a special handler method, rather than catch the exception in the syntax block. This method takes two parameters: the calculation result (successful completion) and throws an exception (if not completed normally some calculation step).

In the following example, we use the handle method provides a default value when greeting asynchronous computation is completed, since no name:

String name = null;

// ...

CompletableFuture<String> completableFuture  
  =  CompletableFuture.supplyAsync(() -> {
      if (name == null) {
          throw new RuntimeException("Computation error!");
      }
      return "Hello, " + name;
  })}).handle((s, t) -> s != null ? s : "Hello, Stranger!");

assertEquals("Hello, Stranger!", completableFuture.get());

Alternatively, suppose we want to use the value manually Future, as shown in the first example, it may be used to accomplish this exception. The completeExceptionally method is intended for this. completableFuture.get following example () method throws ExecutionException, and as RuntimeException reasons:

CompletableFuture<String> completableFuture = new CompletableFuture<>();

// ...

completableFuture.completeExceptionally(
  new RuntimeException("Calculation failed!"));

// ...

completableFuture.get(); // ExecutionException

In the above example, we can use the handle asynchronous exception processing method, but using the get method, we can use a more typical synchronous exception handling.

10. The method of asynchronous

Most methods of API fluid CompletableFuture class has two variants having additional Async suffix. These methods are typically used to run the appropriate step in another thread.

Async suffix no way to run using the calling thread next implementation phase. Async method takes no parameters used Executor use ForkJoinPool.commonPool () method of public access to the Executor fork / join pool implementation to run a step. Async method parameters used with Executor Executor passed a running step.

This example is a modified example uses a Function of calculation processing. The only visible difference is thenApplyAsync method. But behind the scenes in the application, the function is packaged into ForkJoinTask instances (for more information about the fork / join framework, see the article "Java in the Fork / Join Framework Guide"). This can further parallelize your computer and more efficient use of system resources.

CompletableFuture<String> completableFuture  
  = CompletableFuture.supplyAsync(() -> "Hello");

CompletableFuture<String> future = completableFuture
  .thenApplyAsync(s -> s + " World");

assertEquals("Hello World", future.get());

11. JDK 9 CompletableFuture API

In the Java 9, CompletableFuture API by following changes have been further enhanced:

  • The new factory method increases
  • Support delays and timeouts
  • Improved support of subclasses.

It introduced a new instance of API:

  • Executor defaultExecutor()
  • CompletableFuture newIncompleteFuture()
  • CompletableFuture copy()
  • CompletionStage minimalCompletionStage()
  • CompletableFuture completeAsync(Supplier<? extends T> supplier, Executor executor)
  • CompletableFuture completeAsync(Supplier<? extends T> supplier)
  • CompletableFuture orTimeout(long timeout, TimeUnit unit)
  • CompletableFuture completeOnTimeout(T value, long timeout, TimeUnit unit)

We still have a number of static utility methods:

  • Executor delayedExecutor(long delay, TimeUnit unit, Executor executor)
  • Executor delayedExecutor(long delay, TimeUnit unit)
  • CompletionStage completedStage(U value)
  • CompletionStage failedStage(Throwable ex)
  • CompletableFuture failedFuture(Throwable ex)

Finally, in order to solve the timeout problem, Java 9 and the introduction of two new features:

  • orTimeout()
  • completeOnTimeout()

image

Welcome to the number of public attention: " Java confidant " public concern number, reply " 1024 " you know, receive a free 30 classic programming books . I am concerned about the progress with 100,000 programmers. Java knowledge is updated daily Oh, look forward to your arrival!

Guess you like

Origin blog.csdn.net/feilang00/article/details/88019116