CompletableFuture is an advanced multi-threaded feature that supports custom thread pools and system default thread pools. CompletableFuture is a multi-threaded, high-concurrency, often used method that is easier to use than directly creating a thread.

The main contents of this paper are as follows:

  • CompletableFuture basic description
  • API classification and memory rules
  • Create CompletableFuture
  • Value and status test
  • Controls CompletableFuture execution
  • CompletableFuture Behavior continuation

CompletableFuture Basic overview

CompletableFuture is primarily used for asynchronous calls, encapsulating a thread pool that can asynchronously process requests or processes. There are three ways to create a Thread: inherit Thread, implement Runnable interface, and implement Callable interface. To illustrate asynchronous behavior, take an example from life: rice cooker steaming.

Before, it was a big pot of rice, put rice, put water, and then need to constantly add firewood, people want to see when the fire, the specific time to cook, also have to occasionally open to see if the pot is boiling, boiled. This is a Runnable that has no notification, no return value, and just cooks the food. It’s up to you to decide if it’s cooked.

A boss saw the opportunity and asked if he could make something that would automatically cook rice without having to look at it all the time, so rice cookers came into being. The emergence of the first generation of rice cookers, be regarded as the liberation of manpower, no longer need to see the fire, a lot of convenience, they can do other things, heat a milk, cut an egg, but as for when the rice cooked, they have to take a look at the past at intervals. This is the way the Future works. Tasks are executed asynchronously, but in order to get the result, you need to fetch it yourself.

Time continues to advance, the boss has a new idea, every once in a while, to check whether the rice is cooked or a little waste of my time watching TV, the rice cooker is ready, tell me, so THAT I can eat directly on the line. Hence the advanced rice cookers that can be booked, timed and kept warm. This corresponds to the CompletableFuture, where everything can be done automatically, either by calling back a notification when it’s done, or by waiting for it to happen.

  • A Runnable is an action that returns no results.
  • A Callable is an action that returns a result.
  • The Future asynchrony encapsulates Callable and Runnable, delegating execution to a thread pool and retrieving the results of execution
  • The CompletableFuture encapsulates the Future, giving it the ability to call back to the next action after an action has been completed.
Sample code:  public static void main(String[] args){ CompletableFuture future = CompletableFuture.supplyAsync(() -> { System.out.println("The rice cooker starts cooking.");
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "White rice";
    }).thenAccept(result -> {
        System.out.println("Start eating rice.");
    });

    System.out.println("I'm gonna go get some milk and eggs."); future.join(); } The cooker starts cooking. I'll get some milk and eggs and start eating riceCopy the code

This allows you to do other things while you wait for the rice to cook.

API method classification and memory rules

CompletableFuture provides about 50 methods. It is cumbersome to memorize only one by one, so it is divided into the following categories:

Create a class

  • CompleteFuture can be used to create a default return value
  • RunAsync Executes asynchronously, with no return value
  • SupplyAsync Executes asynchronously, with a return value
  • AnyOf After any execution is complete, you can proceed to the next action
  • AllOf completes all missions before proceeding to the next mission

Status value class

  • Join merge result, wait
  • Get Merges wait results and increases the timeout period. Get differs from Join, which only throws unchecked exceptions. Get returns concrete exceptions
  • GetNow Returns the result or exception if the result has been evaluated or is abnormal. Otherwise, the value of valueIfAbsent is returned
  • isCancelled
  • isCompletedExceptionally
  • isDone

The control class is used to actively control the completion behavior of the CompletableFuture

  • complete
  • completeExceptionally
  • cancel

The most important feature of the continuation class CompletableFuture, without which the CompletableFuture would be meaningless, is used to inject callback behavior.

  • thenApply, thenApplyAsync
  • thenAccept, thenAcceptAsync
  • thenRun, thenRunAsync
  • thenCombine, thenCombineAsync
  • thenAcceptBoth, thenAcceptBothAsync
  • runAfterBoth, runAfterBothAsync
  • applyToEither, applyToEitherAsync
  • acceptEither, acceptEitherAsync
  • runAfterEither, runAfterEitherAsync
  • thenCompose, thenComposeAsync
  • whenComplete, whenCompleteAsync
  • handle, handleAsync
  • exceptionally

There are many methods above, we do not need to memorize, according to the following rules, will be a lot more convenient, memory rules:

  1. Methods ending with Async are all asynchronous methods, and corresponding methods without Async are synchronous methods. Generally, one asynchronous method corresponds to one synchronous method.
  2. Methods that end with the Async suffix have two overloaded methods, one that uses a content forkjoin thread pool, and one that uses a custom thread pool
  3. A method that begins with run must have no entry argument and no return value, similar to the Runnable method.
  4. Methods that start with “supply” also have no entry parameters, but return values
  5. Methods that start or end with Accept have an entry argument but no return value
  6. Methods that start or end with Apply have an entry parameter and a return value
  7. A method with the suffix either means that the one who finishes first will be consumed

If you remember the above, you can basically remember most of the methods, and the rest of the methods can be memorized separately.

Create CompletableFuture

Creating the CompletableFuture is essentially delegating the rice we want to cook to the cooker; To cook rice, we need to prepare a couple of things, one we need to decide how to make the rice, and two, we need to specify a rice cooker. In addition, we can delegate other things, and finally we can combine them with all or any.

CompletableFuture C1 = CompleTableFuture.runasync (()->{system.out.println ()) {system.out.println ("Flip the switch, start making it, and leave it at that.")}); CompletableFuture C11 = CompleTableFuture.runasync (()->{system.out.println ())"Flip the switch, start making it, and leave it at that.")},newSingleThreadExecutor()); CompletableFuture<String> c2 = CompletableFuture.supplyAsync (()->{system.out.println ()"Wash the rice");return "Clean rice."; }); Completablefuture.anyof (c1,c2); // CompletableFuture.allof (c1,c2); // CompletableFuture.allof (c1,c2);Copy the code

Value and state

Future.get () = future.get() = future.get() = future.get() = future.get() = future.get() = future.get() = future.get() future.get(1,TimeUnit.Hours)Copy the code

Controls CompletableFuture execution

3 ways to complete future.complete("Rice"); Abnormal / / future.com pleteExceptionally (); // Cancel the argument. It has no real meaning. future.cancel(false);

Copy the code

Continuation behavior is used to describe what should be done after the last thing is done

There are many ways to continue, which can be summarized as the following three types:

  1. CompletableFuture + (Runnable,Consumer,Function)
  2. CompletableFuture + CompletableFuture
  3. CompletableFuture + processes the result

Connection mode 1

CompletableFuture future = CompletableFuture.supplyAsync(()->{
    System.out.println("Pour and wash the rice ingredients.");
    return "Clean rice without novel coronavirus.";
}).thenAcceptAsync(result->{
    System.out.println("Power up, set the mode, start cooking rice.");
}).thenRunAsync(()->{
    System.out.println("The rice is ready to eat.");
})

Copy the code

Connection mode 2

If steamed rice, hot milk, stir-fry, etc., are already three different CompletableFutures, you can use the connection method 2 to combine two or more CompletableFutures together.

CompletableFuture rice = CompletableFuture.supplyAsync(()->{
    System.out.println("Start making rice and get cooked rice.");
    return "Cooked rice."; }) / / rice cooked at the same time, I had milk and CompletableFuture mike. = CompletableFuture supplyAsync (() - > {System. Out. Println ("Begin to heat the milk and get the heated milk.");
    return "Warmed milk."; }); // I think both of them are ready before I have breakfast.thenMike. thenCombineAsync(rice,(m,r)->{system.out.println ("I harvest my breakfast:"+m+","+r);
    returnm+r; Mike. ThenAcceptBothAsync (rice,(m,r)->{system.out.println ("I harvest my breakfast:"+m+","+r); }); Mike. RunAfterBothAsync (rice,()->{system.out.println ()"I harvested my breakfast."); }); / / or connected directly to the two CompletableFuture rice. ThenComposeAsync (r - > CompletableFuture. SupplyAsync (() - > {System. Out. Println ("Start boiling the milk.");
    System.out.println("Start cooking the rice at the same time.");
    return "mike";
}))

Copy the code

Connection mode 3

If we only want to do result processing, there are no other continuations, and we want to determine the exception, we can use continuations 3

WhenCompleteAsync: processing is completed or exception, has no return value handleAsync: processing is completed or abnormal, have return value CompletableFuture supplyAsync (() - > {System. Out. Println ("Start steaming rice.");
    return "Cooked rice.";
}).whenCompleteAsync((rich,exception)->{
    if(exception! =null){ System.out.println("The cooker is broken. The rice is not cooked.");
    }else{
        System.out.println("The rice is ready to eat."); }}) / / returns a value CompletableFuture. SupplyAsync (() - > {System. Out. Println ("Start steaming rice.");
    return "Cooked rice.";
}).handleAsync((rich,exception)->{
    if(exception! =null){ System.out.println("The cooker is broken. The rice is not cooked.");
    }else{
        System.out.println("The rice is ready to eat.");
    }
    return "Prepare to chill the rice before you eat it."; }) / / exception handling CompletableFuture. SupplyAsync (() - > {System. Out. Println ("Start steaming rice.");
    return "Cooked rice.";
}).handleAsync((rich,exception)->{
    if(exception! =null){ System.out.println("The cooker is broken. The rice is not cooked.");
    }else{
        System.out.println("The rice is ready to eat.");
    }
    return "Prepare to chill the rice before you eat it."; }). Exceptionally ((exception)->{// The prefix action must be an operation that returns a valuereturn "";
});

Copy the code

CompletableFuture is actually a little easier to use. It inadvertently makes asynchronous processing, and supports a custom thread pool. If combined with stream, it can easily achieve multi-threaded concurrent processing.

List<CompletableFuture<YoutubeVideoEntity>> futures = subVideosList.stream() .map(item -> CompletableFuture.supplyAsync(() -> this.getRetry(item) , ThreadPoolHolder.BG_CRAWLER_POOL) ).collect(Collectors.toList()); List<YoutubeVideoEntity> videoEntities = futures.stream().map(CompletableFuture::join) .filter(item -> item ! = null && item.getVideoId() ! = null).collect(Collectors.toList());Copy the code