How to write elegant asynchronous code - CompletableFuture

Foreword

In our consciousness, synchronous execution of the program are more in line with the way people think, but is usually not a good thing asynchronous processing. In the case of an asynchronous computation operation callback represented tend dispersed in the code, it may be nested inside each other, if required a step wherein the processing error may occur, the situation becomes worse. Java 8 introduces many new features, which will contain the CompletableFuture introduction class, which makes us easier to write asynchronous code is legible, the class is very powerful, containing more than 50 methods. . .

What is CompletableFuture

CompletableFutureClass design was inspired by Google Guavathe ListenableFuture class that implements Futureand CompletionStagethe interface and added a number of methods, which supports lambda, through the use of non-blocking callback method to enhance the asynchronous programming model. It allows us to pass on the main application thread with a different thread (ie asynchronous) to run the task, and the progress of the main thread notification task completion or failure to write non-blocking code.

Why introduce CompletableFuture

JavaThe 1.5 release introduces Future, you can put it simply understood as the result of the operation of a placeholder, it provides two methods to get the result of the operation.

  • get(): This method is called thread will wait indefinitely for the result of the operation.
  • get(long timeout, TimeUnit unit): This method is called thread only at a specified time timeoutto wait for results within, if the wait timeout will throw TimeoutExceptionan exception.

FutureYou may be used Runnableor Callableinstance submitted to the task, its source code can be seen, as it exists several problems:

  • Blocking call get()method will block until wait until the calculation is complete, it does not provide any method can be notified of completion, it also does not have the additional function callback function.
  • Chained calls polymerization process and results , in many cases we would like to link more Futureto complete the lengthy calculations, this time need to consolidate the results and send the results to another task, the interface is difficult to complete this process.
  • Exception handling Future does not provide any way of exception handling.

These problems CompletableFutureare already solved, let's look at how to use CompletableFuture.

How to create CompletableFuture

The easiest way is to call the create CompletableFuture.completedFuture(U value)method to obtain a completed CompletableFutureobject.

@Test
public void testSimpleCompletableFuture() {
    CompletableFuture<String> completableFuture = CompletableFuture.completedFuture("Hello mghio");
    assertTrue(completableFuture.isDone());
    try {
        assertEquals("Hello mghio", completableFuture.get());
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
}

Note that if we do not complete the CompleteableFuturecall get, it will be because the method Futureis not complete, so the getcall will block forever, then you can use CompletableFuture.completethe manual method to complete Future.

Asynchronous processing tasks

When we want to deal with the results of the program to perform tasks asynchronously in the background without concern for the task, you can use the runAsyncmethod, which receives a Runnableparameter of type return CompletableFuture<Void>.

@Test
public void testCompletableFutureRunAsync() {
    AtomicInteger variable = new AtomicInteger(0);
    CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> process(variable));
    runAsync.join();
    assertEquals(100, variable.get());
}

public void process(AtomicInteger variable) {
    System.out.println(Thread.currentThread() + " Process...");
    variable.set(100);
}

If we want to perform tasks in the background and asynchronous processing results need to get the job, you can use supplyAsyncthe method, the method receives a Supplier<T>type of a parameter to return CompletableFuture<T>.

@Test
public void testCompletableFutureSupplyAsync() {
    CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(this::process);
    try {
        assertEquals("Hello mghio", supplyAsync.get()); // Blocking
    } catch (ExecutionException | InterruptedException e) {
        e.printStackTrace();
    }
}

public String process() {
    return "Hello mghio";
} 

See here you may have a problem, perform above runAsyncand supplyAsyncthreaded task is to come from, who created it? It is actually 8 and Java parallelStreamsimilar, CompletableFuturebut also from the global ForkJoinPool.commonPool()to perform these tasks thread obtained. Meanwhile, the above two methods also provide a custom thread pool to perform the task, in fact, if you go to find out about CompletableFuturethe source code, you will find that APIall methods have overloaded versions, with or without custom Executorexecution device.

@Test
public void testCompletableFutureSupplyAsyncWithExecutor() {
    ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
    CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(this::process, newFixedThreadPool);
    try {
        assertEquals("Hello mghio", supplyAsync.get()); // Blocking
    } catch (ExecutionException | InterruptedException e) {
        e.printStackTrace();
    }
}

public String process() {
    return "Hello mghio";
}

Chain polymerization process calls and results

We know that CompletableFuturethe get()method will remain 阻塞until get to the results, CompletableFutureproviding thenApply, thenAcceptand thenRunother methods to avoid this situation, but we can also add a callback notification after the task is completed. These methods use scenario is as follows:

  • thenApply when if we are to from Futurewhen you run the business after receiving the custom code value before the task, and then return some value to this task, you can use this method
  • thenAccept If we want to from Futuretime after receiving a number of business value code to run custom tasks before the results do not care about the return value, you can use this method
  • thenRun If we want business code in the Future finished running custom, and do not want to do this any return value, you can use this method
@Test
public void testCompletableFutureThenApply() {
    Integer notificationId = CompletableFuture.supplyAsync(this::thenApplyProcess)
        .thenApply(this::thenApplyNotify) // Non Blocking
        .join();
    assertEquals(new Integer(1), notificationId);
}

@Test
public void testCompletableFutureThenAccept() {
    CompletableFuture.supplyAsync(this::processVariable)
        .thenAccept(this::thenAcceptNotify) // Non Blocking
        .join();
    assertEquals(100, variable.get());
}

@Test
public void testCompletableFutureThenRun() {
    CompletableFuture.supplyAsync(this::processVariable)
        .thenRun(this::thenRunNotify)
        .join();
    assertEquals(100, variable.get());
}

private String processVariable() {
    variable.set(100);
    return "success";
}

private void thenRunNotify() {
    System.out.println("thenRun completed notify ....");
}

private Integer thenApplyNotify(Integer integer) {
    return integer;
}

private void thenAcceptNotify(String s) {
    System.out.println(
    String.format("Thread %s completed notify ....", Thread.currentThread().getName()));
}

public Integer thenApplyProcess() {
    return 1;
}

If a large number of asynchronous computation, so we can continue to deliver value to a callback from the callback to another, that is, using a chained call mode, it uses very simple.

@Test
public void testCompletableFutureThenApplyAccept() {
    CompletableFuture.supplyAsync(this::findAccountNumber)
        .thenApply(this::calculateBalance)
        .thenApply(this::notifyBalance)
        .thenAccept((i) -> notifyByEmail()).join();
}

private void notifyByEmail() {
    // business code
    System.out.println("send notify by email ...");
}

private Double notifyBalance(Double d) {
    // business code
    System.out.println(String.format("your balance is $%s", d));
    return 9527D;
}

private Double calculateBalance(Object o) {
    // business code
    return 9527D;
}

private Double findAccountNumber() {
    // business code
    return 9527D;
}

Compare Careful friends may have noticed that in all the previous examples of several methods, all methods are executed on the same thread. If we want these tasks to run on a separate thread, then we can use these methods corresponding to the asynchronous version.

@Test
public void testCompletableFutureApplyAsync() {
    ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
    ScheduledExecutorService newSingleThreadScheduledExecutor = Executors
        .newSingleThreadScheduledExecutor();
    CompletableFuture<Double> completableFuture =
        CompletableFuture
            .supplyAsync(this::findAccountNumber,
                newFixedThreadPool) // 从线程池 newFixedThreadPool 获取线程执行任务
            .thenApplyAsync(this::calculateBalance,
                newSingleThreadScheduledExecutor)
            .thenApplyAsync(this::notifyBalance);
    Double balance = completableFuture.join();
    assertEquals(9527D, balance);
}

Results of the implementation process

thenComposeMethods suitable for the task dependency treatment, such as an account balance of business computing: First, we must first find an account, and then calculate the balance of the account, then the calculation is complete before sending notification. All of these tasks are dependent on the previous job return CompletableFutureresults, then we need to use the thenComposemethod, in fact, somewhat similar to Java 8 stream flatMapoperations.

@Test
public void testCompletableFutureThenCompose() {
    Double balance = this.doFindAccountNumber()
        .thenCompose(this::doCalculateBalance)
        .thenCompose(this::doSendNotifyBalance).join();
    assertEquals(9527D, balance);
}

private CompletableFuture<Double> doSendNotifyBalance(Double aDouble) {
    sleepSeconds(2);
    // business code
    System.out.println(String.format("%s doSendNotifyBalance ....", Thread.currentThread().getName()));
    return CompletableFuture.completedFuture(9527D);
}

private CompletableFuture<Double> doCalculateBalance(Double d) {
    sleepSeconds(2);
    // business code
    System.out.println(String.format("%s doCalculateBalance ....", Thread.currentThread().getName()));
    return CompletableFuture.completedFuture(9527D);
}

private CompletableFuture<Double> doFindAccountNumber() {
    sleepSeconds(2);
    // business code
    System.out.println(String.format("%s doFindAccountNumber ....", Thread.currentThread().getName()));
    return CompletableFuture.completedFuture(9527D);
}

private void sleepSeconds(int timeout) {
    try {
        TimeUnit.SECONDS.sleep(timeout);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

thenCombineThe method is mainly used to combine the processing results of a plurality of separate tasks. Suppose we need to find a person's name and address, you can use different tasks to obtain respectively, and then get the results you want complete information on the person's (name + address), you need to merge the two approaches, then we can use the thenCombinemethod .

@Test
public void testCompletableFutureThenCombine() {
    CompletableFuture<String> thenCombine = this.findName().thenCombine(this.findAddress(), (name, address) -> name + address);
    String personInfo = thenCombine.join();
    assertEquals("mghio Shanghai, China", personInfo);
}

private CompletableFuture<String> findAddress() {
    return CompletableFuture.supplyAsync(() -> {
        sleepSeconds(2);
        // business code
        return "Shanghai, China";
    });
}

private CompletableFuture<String> findName() {
    return CompletableFuture.supplyAsync(() -> {
        sleepSeconds(2);
        // business code
        return "mghio ";
    });
}

Wait for execution to complete multiple tasks

In many cases, we want to run multiple tasks in parallel, and then perform some processing all tasks completed. Suppose we want to find three different user name and combine the results. At this point you can use the CompletableFuturestatic method allOf, the method waits until all tasks are completed, should be noted that this method does not return all the tasks it combined results, so the results we need a combination of manual tasks.

@Test
public void testCompletableFutureAllof() {
    List<CompletableFuture<String>> list = Lists.newArrayListWithCapacity(4);
    IntStream.range(0, 3).forEach(num -> list.add(findName(num)));

    CompletableFuture<Void> allFuture = CompletableFuture
        .allOf(list.toArray(new CompletableFuture[0]));

    CompletableFuture<List<String>> allFutureList = allFuture
        .thenApply(val -> list.stream().map(CompletableFuture::join).collect(Collectors.toList()));

    CompletableFuture<String> futureHavingAllValues = allFutureList
        .thenApply(fn -> String.join("", fn));

    String result = futureHavingAllValues.join();
    assertEquals("mghio0mghio1mghio2", result);
}

private CompletableFuture<String> findName(int num) {
    return CompletableFuture.supplyAsync(() -> {
        sleepSeconds(2);
        // business code
        return "mghio" + num;
    });
} 

Exception Handling

In fact, an exception is not a good deal in a multithreaded program, but fortunately in CompletableFuturethe provided us with a very convenient way of handling exception, in our example above code:

@Test
public void testCompletableFutureThenCompose() {
    Double balance = this.doFindAccountNumber()
        .thenCompose(this::doCalculateBalance)
        .thenCompose(this::doSendNotifyBalance).join();
}

In the above code, the three methods doFindAccountNumber, doCalculateBalanceand doSendNotifyBalanceas long as any abnormality occurs, the following method call will not run.
CompletableFutureProviding three ways to handle exceptions, respectively exceptionally, handleand whenCompletemethods. The first way is to use the exceptionallymethod to handle exceptions, if the previous method fails and an exception occurs, the exception callback is called.

@Test
public void testCompletableFutureExceptionally() {
    CompletableFuture<Double> thenApply = CompletableFuture.supplyAsync(this::findAccountNumber)
        .thenApply(this::calculateBalance)
        .thenApply(this::notifyBalance)
        .exceptionally(ex -> {
            System.out.println("Exception " + ex.getMessage());
            return 0D;
        });
    Double join = thenApply.join();
    assertEquals(9527D, join);
}

The second way is to use a handlemethod handle exceptions, and the way to handle exceptions than the above exceptionallyis more flexible way, we can get to both the current exception object and processing results.

@Test
public void testCompletableFutureHandle() {
    CompletableFuture.supplyAsync(this::findAccountNumber)
        .thenApply(this::calculateBalance)
        .thenApply(this::notifyBalance)
        .handle((ok, ex) -> {
            System.out.println("最终要运行的代码...");
            if (ok != null) {
            System.out.println("No Exception !!");
            } else {
            System.out.println("Exception " + ex.getMessage());
            return -1D;
            }
            return ok;
        });
}

A third is to use a whenCompletemethod of treating abnormalities.

@Test
public void testCompletableFutureWhenComplete() {
    CompletableFuture.supplyAsync(this::findAccountNumber)
        .thenApply(this::calculateBalance)
        .thenApply(this::notifyBalance)
        .whenComplete((result, ex) -> {
            System.out.println("result = " + result + ", ex = " + ex);
            System.out.println("最终要运行的代码...");
        });
}

to sum up

In this paper, we introduce the CompletableFuturefollowing part of the method and use class methods of this class a lot while providing the functionality is very powerful, more use of asynchronous programming, familiar with the basic usage to understand or to in-depth source analysis and implementation principles.

Guess you like

Origin www.cnblogs.com/mghio/p/12650256.html