This article has participated in the “Digitalstar Project” to win a creative gift package and challenge the creative incentive money.
Hi, I’m crooked.
Fill in the hole and write the CompletableFuture that I owe because I’ve received several messages urging me to do more.
This is something I mentioned earlier in this post: “The interviewer asked me if I knew the Future of asynchronous programming.”
Because I’m focusing on the Future, CompletableFuture simply writes in the last section:
I’ll just take the example and change it. I’ll put the code here:
public class MainTest { public static void main(String[] args) throws Exception { CompletableFuture.supplyAsync(() -> { System.out.println(thread.currentThread ().getName() +" ); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } return "Makeup done." ; }).whenComplete((returnStr, exception) -> { if (exception == null) { System.out.println(Thread.currentThread().getName() + returnStr); } else {system.out.println (thread.currentThread ().getName()) + ); exception.printStackTrace(); }}); System.out.println(thread.currentThread ().getName() +" ); Thread.currentThread().join(); }}Copy the code
The core need is that while the goddess puts on her makeup, I can go do my own thing first.
The above program runs like this:
It’s as we expected. There’s nothing wrong with it.
But when you write your own programs, you might run into something like this:
What’s going on? The goddess is still putting on makeup, and the program is done?
Yes, that’s my first point about CompletableFuture: daemon threads.
Daemon thread
If you look closely at the two screenshots mentioned above and compare them to line 28, the second screenshot is missing a line of code:
Thread.currentThread().join();
What is this line of code doing?
The goal is to block the main thread, even if you put the main thread to sleep, but the goal is to block the main thread.
Without this line of code, what would happen is that the main thread would just run and the program would be finished.
What do you think could be the reason?
The concept of daemon threads should pop into your mind at this point.
The main thread is the user thread, that’s fine.
After all user threads have executed, the JVM exits.
Therefore, I have a reasonable reason to suspect that the tasks being performed inside the CompletableFuture belong to the daemon thread.
With the theoretical knowledge behind it and the hypothesis, and the direction of confirmation, the problem is simple.
Click here to create a breakpoint and Debug the expression to see that it is indeed a daemon thread:
I usually want to see the actual code, but I don’t want to stop until I see the line of code that sets this thread as a daemon thread.
So I went and looked for it, and it took me a little bit of time, and I’m not going to describe the process, but I’m going to talk about the conclusion.
First, the default thread pool for CompletableFuture is ForkJoinPool, which can easily be found in the source code:
Here is the place in ForkJoinPool where all threads are set to daemons:
java.util.concurrent.ForkJoinPool#registerWorker
If you want to debug on your own, after setting a breakpoint here, you can look at the call stack and quickly understand the flow of the call:
In addition, I noticed this note in the process of writing the article:
Shutdown and shutdownNow are useless for this thread pool.
If tasks in the thread pool need to be completed before the program terminates, commonPool().awaitquiescence should be called before exiting.
So, my program should look like this:
Yes, it’s nice. It’s very elegant.
If your asynchronous task is so important that it must be executed, ForkJoinPool has a method wrapped around it:
java.util.concurrent.ForkJoinPool#quiesceCommonPool
CompletableFuture also supports passing a custom thread pool:
For example, I changed the previous program to the following:
Add logic to the specified thread pool, comment out the main thread join code, and run. Alas, the JVM has always been around.
Isn’t that amazing?
I don’t think I need to analyze that, do I?
And comparison of the Future
CompletableFuture is essentially an upgrade to Future.
Future has it. It has it all.
Future’s weakness, it fills in.
After all, one is JDK 1.5 and the other 1.8:
Ten years in between. Ten years!
So, come from behind.
I want to compare the Future to the CompletableFuture.
First of all, for me personally, the first and most intuitive feeling is that the posture for obtaining results is much more comfortable.
I’ll have to pull this out again, focusing on the following two types of future and callback:
What happens when we use a Future to implement asynchrony and get asynchronous results?
Call the future.get() method.
If the value is ready at this point, encapsulated in the Future, then all is well, just pull it out and use it.
But what if the value isn’t ready yet?
Is it blocked waiting?
That’s why I often say Future is a castrated version of asynchronous mode.
For example, again in the original example, if I were to use Future, it would look like this:
If you look at what I’ve framed, the main thread starts getting the result, and the action of getting the result blocks the main thread.
You won’t be able to wash your hair, man.
Okay, so you said you put the operation to get the results last, okay.
However, no matter where you put it, you have a get action, and when you perform that action, you don’t know whether the value is ready or not, so you can block waiting.
Ok, so the question is: what if you eliminate the blocking wait?
It’s easy. Think differently. We go from asking, to waiting.
Goddess makeup is good, take the initiative to inform me not good?
In programmer’s words: when the result comes out, you just execute the callback function I left you.
CompletableFuture can do that.
Write CompletableFuture as follows:
Pool-1-thread-1 is the thread for the goddess to make up. She will call you when she is well.
That was the first thing I learned that made me feel comfortable when I first came across CompletableFuture.
Notice that whenComplete(returnStr, exception) return message and exception message are in there.
In addition, this method still returns a value, and you can get the return value just as you would with Future:
In theory, it’s ready to go.
But if you don’t need a return value, it also provides this notation:
Normal and abnormal conditions are handled separately.
Elegant, very elegant.
And even better.
Didn’t the thread used to apply makeup and the thread used to complete makeup be the same:
Suppose we need two different threads, one for makeup and one for notifications. After all, after the goddess finished makeup, more goddess, do two threads I think is not excessive.
The changes were obscenely small:
Simply change the method called from whenComplete to whenCompleteAysn.
Also, this method supports specifying thread pools:
If you look at CompletableFuture there’s a bunch of aysn-ending methods, most of which do this, executed asynchronously in a thread pool.
If that’s enough, here’s one more Future doesn’t have.
So let’s say our demand looks like this.
After the goddess finished makeup, but also spend a little will choose clothes, not too much.
So we now have two asynchronous tasks, the first is to put on makeup, and the second is to choose clothes.
The selection of clothes should be done after the makeup is done, and these two tasks are serial. How can we use CompletableFuture to achieve this?
I’ll post the code, but to make it more intuitive, I’m not using the chain call:
public class MainTest { public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newFixedThreadPool(10); / / task a CompletableFuture < String > makeUpFuture = CompletableFuture. SupplyAsync (() - > { System.out.println(thread.currentThread ().getName() +" ); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } return "Makeup done." ; }, executorService); // Task 2 (makeUpFuture is method caller, CompletableFuture<String> dressFuture = makeupFuture.thenapply (makeUp -> {makeUp -> {makeUp -> {makeUp -> {makeUp -> {makeUp -> {makeUp -> {makeUp -> { System.out.println(thread.currentThread ().getName() + makeUp + 1) ); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } return makeUp +" Go and play, pretty boy." ; }); Dressfuture.thenaccept (result -> {system.out.println (thread.currentThread ().getName() + "-" + result); }); }}Copy the code
The output would look like this:
In line with our expectations.
What if we wanted to switch threads when we wanted to choose clothes?
Async: Async: Async: Async: Async: Async
We talked about multiple asynchronous tasks executing sequentially, and then we’ll talk about parallelism.
CompletableFuture provides two parallel methods:
The input parameters of both methods are mutable parameters, which are asynchronous tasks.
AllOf is, as the name suggests, the multiple CompletableFutures that enter the parameter must succeed in order to continue execution.
And anyOf is multiple completableFutures that enter as long as one of them succeeds.
Let me give you an example.
Hypothetically, I mean hypothetically, I’m an Aquaman.
Anyway, LET’s say I have a friend.
He is chasing after several girl friends at the same time. Today, he’s going out with either Amy or Guizhi, either way. Whoever gets the makeup done first gets a date. The other one stood her up.
This scenario can be simulated using anyOf, resulting in code like this:
Judging from the output, mei ended up dating her friend.
With Amy on a date, we have to go to dinner, right?
Then his friend asked: Mei, what would you like to eat?
Amy would answer: Whatever, whatever, whatever.
Hearing this answer, my friend had a bottom in mind and immediately gave a plan: let’s go to eat Shaxian snacks or Yellow braised chicken, which shop wait for a short time, we will go to eat which one.
So the code above looks like this:
The output looks like this:
I’ll put all the code here so you can run by pasting it:
public class MainTest { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(10); CompletableFuture<String> xiaoMei = CompletableFuture.supplyAsync(() -> { System.out.println(thread.currentThread ().getName() +" ); try { int time = ThreadLocalRandom.current().nextInt(5); TimeUnit.SECONDS.sleep(time); System.out.println(thread.currentThread ().getName()) + "; } catch (InterruptedException e) { e.printStackTrace(); } return "Mei: Makeup is done." ; }, executorService); CompletableFuture<String> xiaoGuai = CompletableFuture.supplyAsync(() -> { System.out.println(thread.currentThread ().getName() +" ); try { int time = ThreadLocalRandom.current().nextInt(5); TimeUnit.SECONDS.sleep(time); System.out.println(thread.currentThread ().getName()) + "; } catch (InterruptedException e) { e.printStackTrace(); } return "Xiaoguai: Makeup is done." ; }, executorService); CompletableFuture<Object> girl = CompletableFuture.anyOf(xiaoMei, xiaoGuai); Girl.thenaccept (result -> {system.out.println (" + result "); }); CompletableFuture<String> eatChooseOne = girl.thenApplyAsync((result) -> { try { TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(10)); } catch (InterruptedException e) { e.printStackTrace(); } return result + "There are few people here, let's go to eat shaxian snacks!" ; }, executorService); CompletableFuture<String> eatChooseTwo = girl.thenApplyAsync((result) -> { try { TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(10)); } catch (InterruptedException e) { e.printStackTrace(); } return result + "There are few people here, let's go to eat yellow braised chicken!" ; }, executorService); Completablefuture.allof (eatChooseOne, eatChooseTwo).thenAccept(result -> {system.out.println (" final result: "+ result); }); }}Copy the code
If you say, kids make choices, adults make all choices.
So, you can try allOf, but note that allOf does not return a value.
Okay, so I’m starting to feel a little bit like API teaching. So there are many, many more ways to CompletableFuture, and I’m not going to go through them all.
Let’s talk about the get method
Finally, look at the get method. After reading the JDK and distributing the source code of this performance problem, I was surprised! After reading this article, some friends have some questions. Let me talk about them again.
CompletableFuture can submit a task in two ways:
One is supplyAsync with a return value.
One is that runAsync returns void, which means there is no return value.
For example, we use supplyAsync:
Deliberately return null.
I can also extend this by assuming that our method uses runAsync and has no return value anyway.
Like this:
Let’s look at the get method again:
You can see that the judgment condition here is r = result == null.
The question is, what if this method returns null anyway, which is the case above?
If null, there are three cases:
- 1. RunAsync does not return a value, so even if the task is complete, get does return null.
- 2. There is a return value, but the task has not completed yet, so result is null.
- 3. There is a return value, which is null.
How do you distinguish these three cases?
So you have to look at where the result is assigned, and you can guess with your toes that something is going on there.
So after a brief search, here’s the key:
The purpose of the boxed code is to get the offset of the Result field in the CompletableFuture class and store it in uppercase result.
CompareAndSwapObject = compareAndSwapObject;
Then you can find these null-related locations:
The answer is the part I’ve framed: inside the CompletableFuture, null is also wrapped into the AltResult object.
Based on this, you can distinguish between the three cases I mentioned earlier.
You can see that there is a special completeNull method where the caller has the AysncRun method:
You can make a breakpoint where it is called and run the code I committed with runAsync earlier:
If you look at the call stack and debug a little bit, you can see how runAsync, which really doesn’t return a value, works.
The core technique is to encapsulate NULL into an AltResult object.
And then how do you return null?
There is already an object representing null, so it is easy to make a simple judgment:
Finally, a word about this method:
java.util.concurrent.CompletableFuture#waitingGet
I wrote this sentence in my previous article:
This spin is added to perform a slightly heavier operation in the park code of the subsequent logic a little later. However, I think the benefits of brief Spin-wait are very small.
Some friends asked me the logic of Park.
It’s actually in the last branch of the waitingGet while loop, which I’ve boxed:
Finally, you can Debug down to find this place:
java.util.concurrent.CompletableFuture.Signaller#block
Here’s park’s logic:
Hit the break point and play by yourself.
In fact, there is a SAO operation, I generally do not tell others, but also simple to share it.
The main thread will block waiting for the result of the asynchronous task because it called the get method:
Just run it and click on the camera icon:
You can see something like this:
The main thread is park, where is it park?
at java.util.concurrent.CompletableFuture$Signaller.block(CompletableFuture.java:1707)
Isn’t that what I just told you?
Then you can create a breakpoint here, look at the call stack, and play the main link clearly:
So, this wave of reverse manipulation, slip or not, you’ll learn in minutes.
Where did you find the park? Where did you get unpark?
Isn’t that easy?
Anyway, I came up with it:
Then make a break point here at unpark:
The wake up process can also be debugged explicitly.
All right, you’ve got the hang and wake in the key, so here we go. Go play.
This article has been collected from personal blog, welcome to play.
www.whywhy.vip/