Java8 — asynchronous programming
Asynchronous programming
Asynchrony is simply implementing a method that lets the operation continue without waiting for a return value from the called function
Create a task and execute it
No arguments to create
1 CompletableFuture<String> noArgsFuture = new CompletableFuture<>();
Copy the code
The corresponding task is passed, with no return value
The runAsync method performs asynchronous computations in the background, but returns no value. Holds a Runnable object.
1CompletableFuture noReturn = CompletableFuture.runAsync((a)->{
2 //Execute logic with no return value
3});
Copy the code
The corresponding task is passed in with a return value
And now we see that it’s returned CompletableFuture
where T is the type of value that you want to return. The Supplier
is a simple functional interface.
1CompletableFuture<String> hasReturn = CompletableFuture.supplyAsync(new Supplier<String> () {
2 @Override
3 public String get() {
4 return "hasReturn";
5 }
6});
Copy the code
Lambda expressions can be used at this point to make the above logic clearer
1CompletableFuture<String> hasReturnLambda = CompletableFuture.supplyAsync(TestFuture::get);
2
3private static String get() {
4 return "hasReturnLambda";
5}
Copy the code
Get the return value
An asynchronous task also has a return value, and when we want to use the return value of an asynchronous task, we can call the CompletableFuture get() block until an asynchronous task has returned a value.
Let’s modify the get() method above to pause it for ten seconds.
1private static String get() {
2 System.out.println("Begin Invoke getFuntureHasReturnLambda");
3 try {
4 Thread.sleep(10000);
5 } catch (InterruptedException e) {
6
7 }
8 System.out.println("End Invoke getFuntureHasReturnLambda");
9 return "hasReturnLambda";
10}
Copy the code
And then call
1public static void main(String[] args) throws ExecutionException, InterruptedException {
2 CompletableFuture<String> funtureHasReturnLambda = (CompletableFuture<String>) getFuntureHasReturnLambda();
3 System.out.println("Main Method Is Invoking");
4 funtureHasReturnLambda.get(a);
5 System.out.println("Main Method End");
6}
Copy the code
As you can see in the output, the current thread is blocked only when the get() method is called.
1Main Method Is Invoking
2Begin Invoke getFuntureHasReturnLambda
3End Invoke getFuntureHasReturnLambda
4Main Method End
Copy the code
Custom return value
Instead of waiting for an asynchronous task to return a value, we can customize the return value at any time by calling the complete() method.
1CompletableFuture<String> funtureHasReturnLambda = (CompletableFuture<String>) getFuntureHasReturnLambda();
2System.out.println("Main Method Is Invoking");
3new Thread(()->{
4 System.out.println("Thread Is Invoking ");
5 try {
6 Thread.sleep(1000);
7 funtureHasReturnLambda.complete("custome value");
8 } catch (InterruptedException e) {
9 e.printStackTrace();
10 }
11 System.out.println("Thread End ");
12}).run();
13String value = funtureHasReturnLambda.get(a);
14System.out.println("Main Method End value is "+ value);
Copy the code
We can see that the output is the value of the new thread, of course, because our asynchronous method is set to wait 10 seconds. If the asynchronous method waits 1 second and the new thread waits 10 seconds, then the output value is the value of the asynchronous method.
1Main Method Is Invoking
2Begin Invoke getFuntureHasReturnLambda
3Thread Is Invoking
4Thread End
5Main Method End value is custome value
Copy the code
Perform asynchronous tasks sequentially
What if you have an asynchronous task whose completion depends on the completion of a previous asynchronous task? Is the get() method called to get the return value and then executed? It’s a bit of a hassle to write this, but CompletableFuture gives us a way to do what we need to do in order to do some asynchronous tasks. ThenApply, thenAccept, and thenRun. The difference between these three methods.
The method name | Whether the return value of the previous task is available | Whether there is a return value |
---|---|---|
thenApply |
Can get | There are |
thenAccept |
Can get | There is no |
thenRun |
Do not get | There is no |
So generally thenAccept and thenRun are used at the end of the call chain. Let’s take a look at a real example.
1//thenApply retrieves the return value of the previous task, as well as the return value
2CompletableFuture<String> seqFutureOne = CompletableFuture.supplyAsync(()-> "seqFutureOne");
3CompletableFuture<String> seqFutureTwo = seqFutureOne.thenApply(name -> name + " seqFutureTwo");
4System.out.println(seqFutureTwo.get());
5
6
7//thenAccept retrieves the return value of the previous task, but has no return value
8CompletableFuture<Void> thenAccept = seqFutureOne
9 .thenAccept(name -> System.out.println(name + "thenAccept"));
10System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- --");
11System.out.println(thenAccept.get());
12
13//thenRun does not get the return value of the previous task, and there is no return value
14System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- --");
15CompletableFuture<Void> thenRun = seqFutureOne.thenRun(() -> {
16 System.out.println("thenRun");
17});
18System.out.println(thenRun.get());
Copy the code
The following information is returned
1seqFutureOne seqFutureTwo
2seqFutureOnethenAccept
3-------------
4null
5-------------
6thenRun
7null
Copy the code
ThenApply and thenApplyAsync
We can see that all three methods have an Async suffix, such as thenApplyAsync. So what’s the difference between methods with Async and methods without the suffix? So let’s compare thenApply to thenApplyAsync, and everything else is the same.
The difference between the two methods is who performs the task. If thenApplyAsync is used, a different thread is executed from ForkJoinPool.commonPool(). If thenApply is used, If the supplyAsync method executes very fast, thenApply is the main thread, or if it executes very slowly, it is the same as the supplyAsync thread. Let’s take a look at how fast supplyAsync executes using sleep.
1//thenApply and thenApplyAsync
2System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- --");
3CompletableFuture<String> supplyAsyncWithSleep = CompletableFuture.supplyAsync(()->{
4 try {
5 Thread.sleep(10000);
6 } catch (InterruptedException e) {
7 e.printStackTrace();
8 }
9 return "supplyAsyncWithSleep Thread Id : " + Thread.currentThread();
10});
11CompletableFuture<String> thenApply = supplyAsyncWithSleep
12 .thenApply(name -> name + "------thenApply Thread Id : " + Thread.currentThread());
13CompletableFuture<String> thenApplyAsync = supplyAsyncWithSleep
14 .thenApplyAsync(name -> name + "------thenApplyAsync Thread Id : " + Thread.currentThread());
15System.out.println("Main Thread Id: "+ Thread.currentThread());
16System.out.println(thenApply.get());
17System.out.println(thenApplyAsync.get());
18System.out.println("-------------No Sleep");
19CompletableFuture<String> supplyAsyncNoSleep = CompletableFuture.supplyAsync(()->{
20 return "supplyAsyncNoSleep Thread Id : " + Thread.currentThread();
21});
22CompletableFuture<String> thenApplyNoSleep = supplyAsyncNoSleep
23 .thenApply(name -> name + "------thenApply Thread Id : " + Thread.currentThread());
24CompletableFuture<String> thenApplyAsyncNoSleep = supplyAsyncNoSleep
25 .thenApplyAsync(name -> name + "------thenApplyAsync Thread Id : " + Thread.currentThread());
26System.out.println("Main Thread Id: "+ Thread.currentThread());
27System.out.println(thenApplyNoSleep.get());
28System.out.println(thenApplyAsyncNoSleep.get());
Copy the code
We can see that the output is
1-------------
2Main Thread Id: Thread[main,5,main]
3supplyAsyncWithSleep Thread Id : Thread[ForkJoinPool.com monPool - worker - 1, 5, the main]------thenApply Thread Id : Thread[ForkJoinPool.com monPool - worker - 1, 5, the main]
4supplyAsyncWithSleep Thread Id : Thread[ForkJoinPool.com monPool - worker - 1, 5, the main]------thenApplyAsync Thread Id : Thread[ForkJoinPool.com monPool - worker - 1, 5, the main]
5-------------No Sleep
6Main Thread Id: Thread[main,5,main]
7supplyAsyncNoSleep Thread Id : Thread[ForkJoinPool.com monPool - worker - 2, 5, the main]------thenApply Thread Id : Thread[main,5,main]
8supplyAsyncNoSleep Thread Id : Thread[ForkJoinPool.com monPool - worker - 2, 5, the main]------thenApplyAsync Thread Id : Thread[ForkJoinPool.com monPool - worker - 2, 5, the main]
Copy the code
If the supplyAsync method is slow, thenApply is executing on the same thread as the supplyAsync method. If the supplyAsync method is fast, thenApply is executing on the same thread as the Main method.
Combination CompletableFuture
There are two ways to combine two CompletableFutures together
thenCompose()
: The second action is performed when the first task is completethenCombine()
: Operations are performed only when both asynchronous tasks are complete
The usage thenCompose ()
Let’s define two asynchronous tasks, assuming that the second scheduled task needs the return value of the first.
1public static CompletableFuture<String> getTastOne(){
2 return CompletableFuture.supplyAsync((a)-> "topOne");
3}
4
5public static CompletableFuture<String> getTastTwo(String s){
6 return CompletableFuture.supplyAsync((a)-> s + " topTwo");
7}
Copy the code
We write using the thenCompose() method
1CompletableFuture<String> thenComposeComplet = getTastOne().thenCompose(s -> getTastTwo(s));
2System.out.println(thenComposeComplet.get());
Copy the code
The output is
1topOne topTwo
Copy the code
If you remember the thenApply() method from earlier, you might think that this method could do something similar with thenApply().
1//thenApply
2CompletableFuture<CompletableFuture<String>> thenApply = getTastOne()
3 .thenApply(s -> getTastTwo(s));
4System.out.println(thenApply.get().get());
Copy the code
But we find that the return value is a nested return type, and getting the final return value requires two get() calls
The usage thenCombine ()
For example, we need to calculate the sum of the return values of two asynchronous methods. The sum operation can only be calculated if the values obtained by two asynchronous methods, so we can use thenCombine() method for calculation.
1CompletableFuture<Integer> thenComposeOne = CompletableFuture.supplyAsync((a) -> 192);
2CompletableFuture<Integer> thenComposeTwo = CompletableFuture.supplyAsync((a) -> 196);
3CompletableFuture<Integer> thenComposeCount = thenComposeOne
4 .thenCombine(thenComposeTwo, (s, y) -> s + y);
5System.out.println(thenComposeCount.get());
Copy the code
Only when both thenComposeOne and thenComposeTwo are complete will the callback function passed to thenCombine be called.
Combine multiple CompletableFutures
Above we combine two CompletableFutures using thenCompose() and thenCombine(). What if we want to combine any number of CompletableFutures? The following two methods can be combined.
allOf()
: Waiting for allCompletableFuture
The callback function will be run only after thatanyOf()
: Just one of themCompletableFuture
When done, the callback function is executed. Note that other tasks are not executed.
Let’s demonstrate the use of both methods
1//allOf()
2CompletableFuture<Integer> one = CompletableFuture.supplyAsync((a) -> 1);
3CompletableFuture<Integer> two = CompletableFuture.supplyAsync((a) -> 2);
4CompletableFuture<Integer> three = CompletableFuture.supplyAsync((a) -> 3);
5CompletableFuture<Integer> four = CompletableFuture.supplyAsync((a) -> 4);
6CompletableFuture<Integer> five = CompletableFuture.supplyAsync((a) -> 5);
7CompletableFuture<Integer> six = CompletableFuture.supplyAsync((a) -> 6);
8
9CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(one, two, three, four, five, six);
10voidCompletableFuture.thenApply(v->{
11 return Stream.of(one,two,three,four, five, six)
12 .map(CompletableFuture::join)
13 .collect(Collectors.toList());
14}).thenAccept(System.out::println);
15
16CompletableFuture<Void> voidCompletableFuture1 = CompletableFuture.runAsync((a) -> {
17 try {
18 Thread.sleep(1000);
19 } catch (Exception e) {
20
21 }
22 System.out.println("1");
23});
Copy the code
We’ve defined six compleTableFutures that wait for all the completableFutures to wait for all the tasks to complete and then output their values.
The use of the anyOf ()
1CompletableFuture<Void> voidCompletableFuture1 = CompletableFuture.runAsync((a) -> {
2try {
3 Thread.sleep(1000);
4} catch (Exception e) {
5
6}
7System.out.println("voidCompletableFuture1");
8});
9
10CompletableFuture<Void> voidCompletableFutur2 = CompletableFuture.runAsync((a) -> {
11try {
12 Thread.sleep(2000);
13} catch (Exception e) {
14
15}
16System.out.println("voidCompletableFutur2");
17});
18
19CompletableFuture<Void> voidCompletableFuture3 = CompletableFuture.runAsync((a) -> {
20try {
21 Thread.sleep(3000);
22} catch (Exception e) {
23
24}
25System.out.println("voidCompletableFuture3");
26});
27
28CompletableFuture<Object> objectCompletableFuture = CompletableFuture
29 .anyOf(voidCompletableFuture1, voidCompletableFutur2, voidCompletableFuture3);
30objectCompletableFuture.get();
Copy the code
Here we’ve defined three CompletableFutures to do some time-consuming task, at which point the first CompletableFuture will complete first. The printed result is as follows.
1voidCompletableFuture1
Copy the code
Exception handling
We learned how CompletableFuture executes asynchronously, how to combine different CompletableFutures, and how to execute completableFutures sequentially. The next important step is what to do if an exception occurs while performing an asynchronous task. So let’s write an example.
1CompletableFuture.supplyAsync((a)->{
2 //An exception occurs
3 int i = 10/0;
4 return "Success";
5}).thenRun((a)-> System.out.println("thenRun"))
6.thenAccept(v -> System.out.println("thenAccept"));
7
8CompletableFuture.runAsync((a)-> System.out.println("CompletableFuture.runAsync"));
Copy the code
As a result, we find that if an exception occurs in one of the execution chains, the next chain will not be executed, but the other CompletableFutures in the main process will still run.
1CompletableFuture.runAsync
Copy the code
exceptionally()
We can use exceptionally for exception handling
1// Handle exceptions
2
3CompletableFuture<String> exceptionally = CompletableFuture.supplyAsync(() -> {
4 // An exception occurred
5 int i = 10 / 0;
6 return "Success";
7}).exceptionally(e -> {
8 System.out.println(e);
9 return "Exception has Handl";
10});
11System.out.println(exceptionally.get());
Copy the code
If the following output is displayed, you can find that the received value is an exception or a customized value can be returned.
1java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
2Exception has Handl
Copy the code
handle()
Calling the handle() method also catches exceptions and returns a custom value. Unlike the () method, the handle() method is called whether an exception occurs or not. Examples are as follows
1System.out.println("------- is abnormal -------");
2CompletableFuture.supplyAsync((a)->{
3 //An exception occurs
4 int i = 10/0;
5 return "Success";
6}).handle((response,e)->{
7 System.out.println("Exception:" + e);
8 System.out.println("Response:" + response);
9 return response;
10});
11
12System.out.println("------- No abnormal -------");
13CompletableFuture.supplyAsync((a)->{
14 return "Sucess";
15}).handle((response,e)->{
16 System.out.println("Exception:" + e);
17 System.out.println("Response:" + response);
18 return response;
19});
Copy the code
As printed below, we can see that the handle() method is called without an exception
1------- An exception occurs -------
2Exception:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
3Response:null
4------- No exception -------
5Exception:null
6Response:Sucess
Copy the code
Source code address
Refer to the article
- Juejin. Cn/post / 684490…
- www.jianshu.com/p/6bac52527…