This article introduces the basic usage of CompletableFuture from an example. However, say more, it is better to start to practice. So I suggest you guys watch it, practice on the computer, and quickly master CompletableFuture.

Personal blog address: sourl.cn/s5MbCm

Abstract:

  • Future VS CompletableFuture
  • CompletableFutureBasic usage

0 x00. Preface

In some business scenarios, we need to use multi-threading to perform tasks asynchronously to speed up the execution of tasks. Java provides two Runnable Future

interfaces to implement asynchronous task logic.

Although Future

can obtain the result of task execution, the method of obtaining the result remains unchanged. We have to use Future#get to block the calling thread, or use polling to determine if the Future#isDone task is finished and get the result.

Neither approach is very elegant, and prior to JDK8 the concurrency library did not provide an implementation for asynchronous callbacks. We have no choice but to use third-party libraries like Guava to extend the Future and add callback support. The relevant codes are as follows:

While this approach enhances Java asynchronous programming capabilities, it does not address scenarios where multiple asynchronous tasks need to depend on each other.

For example, if we need to travel, we need to complete three tasks:

  • Task 1: Book a flight
  • Task 2: Book a hotel
  • Task 3: Order a car rental service

Task 1 and Task 2 are clearly unrelated and can be performed separately. However, task three must wait for task one and task two to complete before ordering a car rental service.

In order to obtain the results of task 1 and Task 2 during task 3, we also need to use CountDownLatch.

0x01. CompletableFuture

After JDK8, Java has added a very powerful class: CompletableFuture. This class alone can easily accomplish the above requirements:

You can forget about the CompletableFuture API for now, and we’ll talk about it.

In contrast to the Future

, CompletableFuture has the advantages of:

  • Instead of manually allocating threads, the JDK allocates them automatically
  • Code semantics clear, asynchronous task chain call
  • Support for scheduling asynchronous tasks

How is it? Is it powerful? Now hold on tight. Nigger’s about to start.

1.1 Overview of Methods

First take a look at the methods provided by this class through the IDE:

Just to count, there are over 50 methods in this class, my God…

But don’t be afraid, little black brother summed up for you, follow the rhythm of little black brother, take you to master CompletableFuture.

If the picture is not clear, you can pay attention to “Program general affairs”, reply: “233”, to obtain the mind map

1.2 Creating an instance of CompletableFuture

To create an instance of the CompletableFuture object we can use the following methods:

The first method creates a CompletableFuture with a default result, which isn’t much of a story. Let’s focus on the following four asynchronous methods.

The first two methods, runAsync, do not support returning values, whereas supplyAsync can support returning results.

By default, these two methods will be executed using the public ForkJoinPool thread pool, which by default is the number of CPU cores.

Can set the JVM option:-Djava.util.concurrent.ForkJoinPool.com mon. Parallelism to set the ForkJoinPool thread pool threads

One drawback of using a shared thread pool is that if one task is blocked, other tasks will not have a chance to execute. Therefore, it is strongly recommended to use the latter two methods to actively create thread pools according to different task types to isolate resources and avoid interference with each other.

1.3 Setting Task Results

CompletableFuture provides the following methods to proactively set the result of a task.

 boolean complete(T value)
 boolean completeExceptionally(Throwable ex)
Copy the code

The first method actively sets the CompletableFuture task execution result, and if true is returned, the setting succeeds. If false is returned, the setup fails because the task has finished and the result has been executed.

Example code is as follows:

// Perform asynchronous tasks
CompletableFuture cf = CompletableFuture.supplyAsync(() -> {
  System.out.println("Cf task execution starts");
  sleep(10, TimeUnit.SECONDS);
  System.out.println("Cf task execution completed");
  return "Nigga downstairs.";
});
//
Executors.newSingleThreadScheduledExecutor().execute(() -> {
  sleep(5, TimeUnit.SECONDS);
  System.out.println("Result of actively setting CF task");
  // Set the task result. The result returns true because the CF task is not finished
  cf.complete("All in all.");
});
// cf will be blocked because it has not finished executing. Five seconds later, another thread takes the initiative to set the result of the task
System.out.println("get:" + cf.get());
// Wait until the CF task is complete
sleep(10, TimeUnit.SECONDS);
// Because the task result has been set, the task result will be discarded after cf execution ends
System.out.println("get:" + cf.get());
/*** * CF task execution starts * Result of setting CF task actively * GET: program communication * CF task execution ends * GET: program communication */
Copy the code

One thing to note here is that once complete is set, the result returned by the CompletableFuture will not be changed, even if the subsequent CompletableFuture task completes.

The second method sets an exception object for the CompletableFuture. If the setting succeeds, an error will be thrown if a method such as get is called to get the result.

Example code is as follows:

// Perform asynchronous tasks
CompletableFuture cf = CompletableFuture.supplyAsync(() -> {
    System.out.println("Cf task execution starts");
    sleep(10, TimeUnit.SECONDS);
    System.out.println("Cf task execution completed");
    return "Nigga downstairs.";
});
//
Executors.newSingleThreadScheduledExecutor().execute(() -> {
    sleep(5, TimeUnit.SECONDS);
    System.out.println("Active setting cf exception");
    // Set the task result. The result returns true because the CF task is not finished
    cf.completeExceptionally(new RuntimeException("Oh, hang up."));
});
// The first 5 seconds will be blocked because cf is not finished. The subsequent program throws an exception and ends
System.out.println("get:" + cf.get());
/ * * * * cf task execution started * active set cf abnormal * Java util. Concurrent. ExecutionException: Java. Lang. RuntimeException: ah, hanged a *... * /
Copy the code

1.4 CompletionStage

CompletableFuture implements the Future and CompletionStage respectively.

The Future interface is familiar, and I’m going to focus on the Complete stage.

Most of the CompletableFuture methods come from the CompletionStage interface, which is what makes CompletableFuture so powerful.

To understand the CompletionStage interface, we need to understand the timing relationships of tasks. Task timing relationships can be divided into the following types:

  • Serial execution relation
  • Parallel execution relation
  • AND convergence relation
  • OR convergence relation

1.5 Serial Execution relationship

Tasks are executed sequentially, and the next task must wait for the previous task to complete before it can continue.

The CompletionStage has four sets of interfaces that can describe the serial relationship:

The thenApply method needs to pass in core arguments of type Function

. The core method of this class is:
,r>

 R apply(T t)
Copy the code

So the interface will take the result of the previous task as an input and return the result at the end of execution.

The thenAccept method needs to pass in an object of type Consumer

. The core method of this class is:

void accept(T t)
Copy the code

As you can see from the return value void, this method does not support returning a result, but requires the result of the previous task execution to be passed in as an argument.

The thenRun method requires an object of type Runnable to be passed in. This class is familiar, and the core method neither supports passing in parameters nor returns the result of execution.

The thenCompose method works the same as thenApply, except that thenCompose returns a new CompletionStage. It’s kind of abstract, but you can think of it as code.

Methods have Async, which means they can be executed asynchronously. The series also has overloaded methods, which can be passed in custom thread pools, which are not shown above, leaving the reader to explore the API for himself.

Finally, we’ll show how thenApply works in code:

CompletableFuture<String> cf
        = CompletableFuture.supplyAsync(() -> "Hello, nigga downstairs.")/ / 1
        .thenApply(s -> s + "@program General") / / 2
        .thenApply(String::toUpperCase); / / 3
System.out.println(cf.join());
// The output result is HELLO, downstairs little black brother @ program general affairs
Copy the code

This code is relatively simple. We start an asynchronous task and then execute the next two tasks sequentially. Task 2 waits for task 1 to complete, and task 3 waits for task 2.

Function

, Consumer

, Runnable

,>

1.6 AND Convergence relationship

The aggregation relation of AND indicates that the next task can be carried out only after all tasks are completed.

As shown above, task C will not be executed until both task A and task B are completed.

The CompletionStage has the following interface to describe this relationship.

The core parameter of thenCombine is BiFunction, which acts the same as Function except that BiFunction can accept two parameters while Function can only accept one.

The thenAcceptBoth method’s core parameter, BiConsumer, also acts like Consumer, except that it takes two arguments.

The runAfterBoth method has the simplest core parameter, which I’ve covered above.

If you want to complete multiple tasks, you need to use CompletableFuture#allOf, but note that this method does not support returning the result of the task.

AND aggregation relation related example code, the beginning has used, here again paste, convenient for everyone to understand:

1.7 OR Convergence relationship

There is AND convergence relation, AND of course there is OR convergence relation. The OR convergence relationship means that once any task of multiple tasks is completed, the next task can be carried out.

The CompletionStage has the following interface to describe this relationship:

The previous three groups of interface method transmission participation AND aggregation relationship is consistent, AND will not be explained in detail here.

Of course the OR aggregation relationship can be used by CompletableFuture#anyOf to perform multiple tasks.

The following sample code shows how to use applyToEither to complete the OR relationship.

CompletableFuture<String> cf
        = CompletableFuture.supplyAsync(() -> {
    sleep(5, TimeUnit.SECONDS);
    return "Hello, nigga downstairs.";
});/ / 1

CompletableFuture<String> cf2 = cf.supplyAsync(() -> {
    sleep(3, TimeUnit.SECONDS);
    return "Hello, program interconnect";
});
// Execute the OR relationship
CompletableFuture<String> cf3 = cf2.applyToEither(cf, s -> s);

Cf2 sleeps only for 3 seconds, so the execution is completed first
System.out.println(cf2.join());
// Result: hello, program common
Copy the code

1.8 Exception Handling

If an exception is raised during the execution of the CompletableFuture method, the exception will be thrown only when get and JOIN are called to obtain the result of the task.

In the code above we show using a try.. Catch handles the above exception. However, this approach is not very elegant, and the CompletionStage provides several methods to gracefully handle exceptions.

Exceptionally is used similarly to try.. Exception handling in a catch block.

WhenComplete and Handle methods are similar to try.. catch.. The finally code block in finanlly. It will be executed regardless of whether an exception occurs. The difference between the two methods is that Handle supports returning results.

The following example code shows the use of handle:

CompletableFuture<Integer>
        f0 = CompletableFuture.supplyAsync(() -> (7 / 0))
        .thenApply(r -> r * 10)
        .handle((integer, throwable) -> {
            // Print the exception if it exists and return the default value
            if(throwable ! =null) {
                throwable.printStackTrace();
                return 0;
            } else {
                / / if
                returninteger; }}); System.out.println(f0.join());/** *java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero * ..... * * * /
Copy the code

0 x02. Summary

JDK8 provides A very powerful function of CompletableFuture, which can orchestrate asynchronous tasks, complete serial execution, parallel execution, AND aggregation relationship, OR aggregation relationship.

However, there are too many methods in this class, and the methods also need to pass in various functional interfaces, novice use will be directly confused. Here is a summary of the three core parameters

  • FunctionThis type of function interface supports both receiving parameters and returning values
  • ConsumerThis type of interface function can only accept parameters, not return values
  • RunnableThis type of interface does not support accepting arguments, nor does it support returning values

After making clear the function parameters function, AND then according to the serial, AND aggregation relationship, OR aggregation relationship to summarize the relevant methods, so that it is easier to understand

Finally, I will post the mind map from the beginning of this article, which I hope will help you.

0x03. Help Documentation

  1. Geek Time – Concurrent programming column
  2. Colobu.com/2016/02/29/…
  3. www.ibm.com/developerwo…

One last word (for attention)

CompletableFuture has been concerned for a long time. I thought it was very easy to use, just like Future, but I found it very difficult when I learned Taoism. The various API methods look a bit too big.

Later I saw the geek time – “Concurrent programming” column using an inductive way to classify CompletableFuture various methods, and I understood it all at once. This article also refers to this kind of induction.

I searched for materials for this article and sorted it out for a week. Fortunately, it was successfully produced today.

In the name of black brother’s hard work, click on the following, give me a thumbs up. Not next time, brother! Writing an article is hard. You need some positive feedback.

If you find something wrong, please leave a message and point it out to me so that I can modify it.

Thank you for reading, I insist on original, very welcome and thank you for your attention ~

Welcome to pay attention to my public account: procedures to get daily dry goods push. If you are interested in my topics, you can also follow my blog: studyidea.cn