preface
Recently I learned a very useful asynchronous functional programming tool CompletableFuture from my leader. I found it was provided by Java8 when I looked at the source code. I was ashamed to know it until now. This class is very comprehensive and powerful, providing a simple chain call function to achieve serialization, parallelism, composition, and so on. In this article, learn CompletableFuture with you.
CompletableFuture profile
Before introducing CompletableFuture, let’s consider a question. If you ask your girlfriend Sakura out to a movie right now, let’s consider it a Task. Think about the stages you might go through to complete this Task. The expected scene may be the first to send wechat or call contact, and then Sakura agreed, sakura began to take a bath, sakura makeup, you buy a ticket, Sakura go out, you also go out, Sakura by transport to the agreed location…… You end up happily sitting in a movie theater watching a movie
A simple phase diagram might look like this, and there are tasks that are not considered. You will find that a task can be divided into multiple smaller tasks. At the same time, you will find that some tasks are dependent on each other, such as Sakura taking a bath and Sakura putting on makeup, and some tasks can be executed simultaneously. For example, sakura takes a bath and you buy a movie ticket. These are two tasks that are not dependent on each other.
CompletableFuture is an improved FutureTask class that implements asynchronous tasks. In addition to the Future interface, It also implements the CompletionStage interface, which is designed with the idea that each task can be broken down into several sub-tasks, and the execution of each sub-task can be abstracted into a CompletionStage
Whenever an exception occurs at any stage of the process, we can call it back, and the exception handling process is also a stage, so we can still start a new CompletableStage after handling the exception. CompletableFuture uses this idea to provide a series of chained methods, realizing that tasks can be arbitrarily serial, parallel and combined with various operations. Completion of one phase can actively trigger the next, which is CompletableFuture’s core strength.
Compared with the FutureTask
CompletableFuture offers the following obvious advantages over its previous use of FutureTask:
-
When FutureTask gets the results of an asynchronous task, it either blocks by calling GET () or polls isDone() for an active query, neither of which is good and forces the main thread to wait. CompletableFuture, on the other hand, can actively notify the next stage after completing a task stage
-
FutureTask doesn’t provide an API for exception handling, and CompletableFuture provides exception callback on every phase. ()
-
FutureTask cannot create a workflow of a task, whereas CompletableFuture can create a workflow of any number of Completionstages
-
CompletableFuture can arbitrarily combine multiple task stages, serial and parallel, using simple APIS to achieve complex operations
If you don’t already know FutureTask, congratulations and learn CompletableFuture directly. If you know anything about FutureTask, forget it and let’s have fun learning CompletableFuture.
CompletableFuture usage
Here’s an overview of the API usage provided by CompletableFuture, Before learning CompletableFuture I want you to understand Java8 lambda and several functional interfaces such as Function
, Consumer
, Supplier
, BiFunction
And so on, because the important API inputs provided by CompletableFuture are almost all functional interfaces.
Submitting an asynchronous Task
Use the following code to submit a simple asynchronous task
CompletableFuture. SupplyAsync (() - > {System. Out. Println (" task "); Return "task result "; });Copy the code
But it’s interesting to note that the introductory case in the main method is very likely to have no print at all on the console. I was a little confused when I was learning…… Then I had an idea. Could it be the end of the main thread? Think about what it means if it is.
Daemon thread
The thread that uses CompletableFuture to execute the task is the daemon thread. We all know that the main thread is the user thread, and when all user threads terminate, the JVM process automatically exits. Debug verifies that the daemo property of the current thread in supplyAsync() is true, indicating that it is a daemon thread like the garbage collector thread.
In the following code example I will omit the main thread sleep code timeunit.seconds.sleep (2);
Gets the result of an asynchronous task
We can use get() and join() to get the result of an asynchronous task. The difference between the two is that join() doesn’t require you to enforce catch or throw exceptions. But they are both blocking methods
CompletableFuture future = CompletableFuture. SupplyAsync (() - > {/ / mission... Return "task result "; }); System.out.println(future.join()); // Get the result of the taskCopy the code
If you look at this, you might say, well, isn’t it still the same as FutureTask where getting the result of the task blocks? Don’t worry, there are plenty of other sexy poses
Task dependent on
When we need to execute two dependent tasks, that is, one asynchronous task can be notified of its completion and the other asynchronous task can be executed with the result of its execution, we can use thenCompose method to do this
CompletableFuture. SupplyAsync (() - > {System. Out. Println (" my wife is cooking "); "Braised spare ribs, steamed sea bass "; }). ThenCompose (dish - > CompletableFuture. SupplyAsync (() - > {/ / on a task is done, then open an asynchronous task System. Out. The println (" wife, notify the food I eat her well: "+dish); return null; }));Copy the code
See, is the result of the first asynchronous task done without blocking to get the result or looping to see if it’s done?
Task to merge
Sometimes we want to proceed to the next stage of operation after both asynchronous tasks are completed. At this time, we can use thenCombine to execute two tasks without dependencies in parallel and operate on the results after both tasks are completed.
CompletableFuture. SupplyAsync (() - > {System. Out. Println (" my girlfriend sakura is doing to braise in soy sauce sparerib "); Return "braised spare ribs "; }). ThenCombine (CompletableFuture supplyAsync (() - > {System. Out. Println (" my girlfriend doing little kwai steamed bass "); Return "Steamed bass "; }),(t1,t2) -> {system.out.println (t1+","+t2+") -> {system.out.println (t1+","+t2+"); ); return ""; });Copy the code
So you’ve probably seen the problem, what if I have two or more tasks that I don’t have dependencies on in parallel? Of course CompletableFuture also gives us an allOf(CompletableFuture
… CFS) method, which can handle any number of parallel tasks.
CompletableFuture < String > future1 = CompletableFuture. SupplyAsync (() - > "task 1"); CompletableFuture < String > future2 = CompletableFuture. SupplyAsync (() - > "completed task 2"); CompletableFuture < String > future3 = CompletableFuture. SupplyAsync (() - > "mission 3"); Completablefuture.allof (future1,future2,future3).thenAccept(t -> {// After all tasks are completed... System.out.println(" All tasks completed, next...") ); });Copy the code
Mission selection
ApplyToEither can be used when you want to move on to the next phase of two asynchronous tasks
CompletableFuture < String >. Bus = CompletableFuture supplyAsync (() - > {System. Out. Println (" my girlfriend about sakura out the movies "); System.out.println(" Girlfriend Sakura is taking a bath, making up..." ); Return "Girlfriend Sakura is ready "; }). ApplyToEither (CompletableFuture supplyAsync (() - > {System. Out. Println (" I ask girlfriend small kwai out the movie "); System.out.println(" girlfriend okui is taking a bath, making up..." ); Return "Girlfriend Kwai is ready "; }), firstReady -> {system.out.println (firstReady+", let's go to the movies "); return firstReady; });Copy the code
That what I want quibble…… Like me so pitiful xianyu warm male normal circumstances is not put sister pigeon, here just example need…… I usually use the thenCombine when I encounter this situation.
So again, like allOf(), CompletableFuture also provides anyOf(CompletableFuture
… CFS) method to achieve a number of parallel tasks can be completed in the next stage of operation.
CompletableFuture < String > future1 = CompletableFuture. SupplyAsync (() - > "task 1"); CompletableFuture < String > future2 = CompletableFuture. SupplyAsync (() - > "completed task 2"); CompletableFuture < String > future3 = CompletableFuture. SupplyAsync (() - > "mission 3"); Completablefuture.anyof (future1, FUture2,future3).thenAccept(t -> {// Any task is completed... System.out.println(" One task completed, next...") ); });Copy the code
Exception handling
The method is called after one or several phases. Exceptionally, it handles exceptions that may occur during the previous phases. It is noted that if you don’t call exceptionally, the result of getting the asynchronous task is not shown. It is important to note that an exception in an asynchronous task will not be displayed. I caught a leadership bug because of this.
CompletableFuture. SupplyAsync (() - > {System. Out. Println (" girlfriend sakura was cooking..." ); If (true) {throw new RuntimeException(" The kitchen is on fire... ); } return null; }).thenapply ((result) -> {system.out.println (" wait for my girlfriend to call me to eat..."). ); return string; });Copy the code
SupplyAsync = supplyAsync; supplyAsync = supplyAsync; After no any reaction, try to ask a just contact with this stuff, the program does not report errors but also did not successfully reach the overdue, this really let head big ah, then made a long time to find that is this reason…… At this point I want to say to the leadership: your ya of stem what, make such a big bug, harm me to waste a long time! Just kidding, it is recommended that you use this method for important phases or manually try catch to avoid errors.
The CompletableFuture API is exceptionally useful if you don’t want to call exceptionally() or try-catch. The handle and whenComplete apis are exceptionally useful if you don’t want to call them. Both methods are called back whether the task ends normally or abnormally. In order to handle as an example
CompletableFuture. SupplyAsync (() - > {System. Out. Println (" girlfriend sakura was cooking "); If (true) {throw new RuntimeException(" The kitchen is on fire..." ); } return "Sakura made lunch "; }). Handle ((s, e) -> {if(e == null){system.out.println (s); } else {system.out.println (e);} system.out.println (e); } return null; });Copy the code
But obviously it looks weird…… You also need to determine if the exception object is null. So use exceptionally().
In much the same way as
If you carefully study CompletableFuture API, you will find that it has many, many, very scary at first glance, but many of these functions are almost the same, only a slight difference, we can be classified. For example,
ThenApply, thenAccept, thenRun
ThenCombine, thenAcceptBoth, runAfterBoth
ApplyToEither, acceptEither, and runAfterEither
Each of the three methods above has almost the same functionality, except that the first API takes the return value of the previous stage as an argument and returns the value after execution. The second API is required to accept the return value of the previous stage as an argument, but there is no return value. The third API requires neither the return value of the previous phase as an argument nor a return value. Therefore, you can choose the appropriate method according to your actual needs.
ApplyToEither, anyOf
ThenCombine, allOf
The difference between the two methods in each set above is that the first API is used for two asynchronous tasks, while the second API can be used for multiple tasks, which means that applyToEither and thenCombine can be implemented, but we can also use anyOf and allOf.
There are many other apis that I won’t go through…… You can use it, try it, read the comments, read the method parameters and return values
Advanced composite
We’ve looked at the three basic and core uses of task merge, task dependency, and task selection. I’m sure you’re smart enough to start thinking about the three combination scenarios, which are actually very easy to implement with proper API calls
For example, in the example above, I need to select Task1 and its stack to complete first to execute Task7, and merge Task2 with Task3, Task4, and Task5 in the stack above Task1. Task3 → Task4 → Task5 Of course this is a very simple combination task, and any complex combination can be done using CompletableFuture.
Imagine that all tasks in the image above contain a combination of tasks in the image above. Only you can think of, there is nothing it can not do!
conclusion
This article briefly introduces the basic design idea and basic use method of CompletableFuture. It has to be said that it really implements a relatively perfect asynchronous programming, and its method inputs use various functional interfaces to transfer behavior and raise the level of abstraction. The next article will introduce the advanced knowledge of CompletableFuture, about thread pooling and performance.