Hello, I am why.
I wrote some articles about thread pools before, and then a classmate went around and found that I had not written an article about the @Async
, so he came to me and asked me:
Yes, I have a showdown.
The reason I don't like this comment is because I have never used it at all.
I am accustomed to using custom thread pools to do some asynchronous logic, and have been using this way for so many years.
So if it is a project I lead, you will definitely not see the @Async
annotations in the project.
Have I seen the @Async
comment before?
I must have seen it before, and some friends like to use this annotation.
Asynchronous development can be done with just one comment, so cool.
I don't know if the person using this annotation knows the principle, anyway I don't know.
A component was introduced during development recently, and it was found that this annotation was used in some places in the method called.
Now that it's used this time, let's study it.
The first thing to note is that this article will not write about thread pool-related knowledge points.
Only describe how I used to understand this annotation that I didn't know before.
Make a demo
I don't know how you would start if you encounter this situation.
But I think no matter what angle you start from, it will definitely fall into the source code in the end.
Therefore, I usually make a Demo first.
Demo is very simple, just three categories.
The first is the startup class, which is nothing to say:
Then make a service:
The syncSay method in this service is annotated @Async
Finally, make a Controller to call it, and it's done:
The demo is set up, and you can start one. It takes more than 5 minutes, so I lose.
Then, start the project, call the interface, and view the log:
Let me go, judging from the thread name, this is not asynchronous?
Why is it still a tomcat thread?
So, I ran into the first problem on the research road: @Async
annotation did not take effect.
Why doesn't it take effect?
Why doesn't it take effect?
I'm also dumbfounded. I said I didn't know anything about this comment before, so how do I know?
What will you do when you encounter this problem?
Of course it is browser-oriented programming!
In this place, if I analyze from the source code, why it didn't work, I can definitely find out the reason.
However, if I program for the browser, it only takes 30 seconds to find these two pieces of information:
Reason for failure:
- 1.
@SpringBootApplication
startup class which did not add@EnableAsync
comment. - 2. There is no Spring proxy class. Because
@Transactional
and@Async
annotations are based on Spring's AOP, and the implementation of AOP is based on the dynamic proxy model. Then the reason for the invalidation of the annotation is obvious. It may be that the method is called by the object itself instead of the proxy object, because it has not been managed by the Spring container.
Obviously, my situation meets the first situation, without adding the @EnableAsync
comment.
For another reason, I am also very interested, but now my first task is to build the Demo, so I can't be tempted by other information.
When many students took questions to inquire, the question originally checked was @Async
annotation did not take effect, but the result slowly went off the road. After fifteen minutes, the problem gradually evolved into the startup process of SpringBoot.
After another half an hour, the webpage will show something like eight-legged essays that must be recite in interviews...
What I mean by this is that just check the problem well. In the process of checking the problem, this question will definitely cause more interesting questions. However, record it and don't let the problem diverge.
This principle is the same as looking at the source code with questions. Looking at it, you may not even know what your problem is.
Okay, come back.
I added this annotation to the startup class:
Initiate the call again:
You can see that the thread name has changed, indicating that it is really just fine.
Now that my demo has been set up, I can start looking for an angle to roll it up.
From the above log, I can also know that there is a task-
that is helping me perform tasks by default.
Speaking of thread pools, I have to know the relevant configuration of this thread pool to rest assured.
So how can I know?
Press first
In fact, the thinking of normal people at this time should be to look through the source code and find the corresponding place to inject the thread pool.
As for me, it's a bit abnormal, I don't bother to look for it in the source code, I want to expose it to me.
How can it be exposed?
Relying on my understanding of thread pools, my first idea is to press this thread pool first.
Suppress it, it can't handle the task under pressure, and let it go into the rejection logic. Normally, it will throw an exception, right?
So, I modified the program a bit:
What I want is to come directly to a wave of vigorous miracles:
result...
It turned out to be...
I have accepted all orders, there is no abnormality?
Typing a few lines per second in the log is very happy:
Although there was no rejection exception that I expected, I still saw a little clue from the log.
For example, I found that this taks is at most 8:
Friends, what do you mean by this?
Does it mean that the configuration of the number of core threads in the thread pool I am looking for is 8?
What, why can't you ask me the maximum number of threads?
is it possible?
Of course it is possible. But when I sent 10,000 tasks, the thread pool rejection strategy was not triggered, so I just used up the largest thread pool?
That is to say, the configuration of this thread pool is the queue length of 9992, and the maximum number of threads is 8?
This is too coincidental and unreasonable, right?
So I think the core thread number configuration is 8, and the queue length should be Integer.MAX_VALUE
.
In order to confirm my guess, I changed the request to this:
num=10 million.
Observe the heap memory usage through jconsole:
That is called a spike, and clicking the [Execute GC] button has no relief.
It is also proved from the side: the tasks may be queued in the queue, causing the memory to soar.
Although, I still don’t know what its configuration is, but after the black box test just now, I have legitimate reasons to suspect:
The default thread pool has the risk of causing memory overflow.
However, it also means that I want to let it throw an exception, and thus expose myself to the sorrow in front of me.
Source code
The previous ideas don't work, so honestly start to understand the source code.
I started from this comment:
After clicking into this comment, a few paragraphs of English, not long, I got a key message from it:
Mainly focus on where I draw the line.
In terms of target method signatures, any parameter types are supported.
In the signature of the target method, input parameters are supported by any type.
One more sentence: when it comes to target methods, when it comes to target, the concept of a proxy object should appear in everyone's mind immediately.
The above sentence is easy to understand, and even feels like a nonsense.
However, it is immediately followed by a However:
However, the return type is constrained to either void or Future.
Constrained means to be restricted or constrained.
This sentence says: The return type is restricted to void or Future.
What do you mean?
Should I return a String?
WTF, the printout is actually null! ?
Then if I return an object here, wouldn't it be easy to burst a null pointer exception?
After reading the notes on the notes, I found the second hidden pit:
If the method is modified by the @Async
annotation, the return value can only be void or Future.
Let's not talk about void, let's talk about this Future.
Look at the other sentence I underlined:
it will have to return a temporary {@code Future} handle that just passes a value through: e.g. Spring's {@link AsyncResult}
There is a temporary, which is a fourth-level vocabulary. What you should know is the temporary and temporary meaning.
Temporary worker, temporary worker, get it.
So it means that if you want to return a value, you can wrap it with an AsyncResult object. This AsyncResult is a temporary worker.
like this:
Then we focus on the value attribute of the annotation:
This note, look at the meaning of the note above, that is, this should be filled with the bean name of a thread pool, which is equivalent to the meaning of specifying the thread pool.
I don't know if I understand it right or not, I will know when I will write a method to verify it.
Okay, so far, I will summarize the information.
- I didn't understand this comment at all before. Now I have a Demo. When building the Demo, I found that in addition to the
@Async
comment, I also need to add the@EnableAsync
comment, such as adding it to the startup class. - Then I tested this default thread pool as a black box. I suspect that its core thread number is 8 by default, and the queue length is long. There is a risk of memory overflow.
- By reading the
@Async
on 06135a061b5cb0, I found that the return value can only be void or Future type. Otherwise, even if other values are returned, no error will be reported, but the returned value is null, and there is a risk of a null pointer. @Async
comment. It should be possible to specify a custom thread pool by looking at the comment.
Next, I put the questions to be explored in order, focusing only on the related questions of @Async
- 1. What is the specific configuration of the default thread pool?
- 2. How does the source code only support void and Future?
- 3. What is the value attribute used for?
What is the specific configuration?
I found the specific configuration is actually a very fast process.
Because the value parameter of this class is simply too friendly:
There are comments in five places where it is called.
The effective call is this one place, just hit the breakpoint first, and then talk about it:
org.springframework.scheduling.annotation.AnnotationAsyncExecutionInterceptor#getExecutorQualifier
After initiating the call, it really ran to the breakpoint:
Debug down along the breakpoint, and you will come to this place:
org.springframework.aop.interceptor.AsyncExecutionAspectSupport#determineAsyncExecutor
The code structure is very clear.
The place numbered ① is to obtain the value @Async
This value is actually the bean name. If it is not empty, the corresponding bean will be obtained from the Spring container.
If value has no value, which is the case of our Demo, it will go to the place numbered ②.
This place is the default thread pool I am looking for.
Finally, whether it is the default thread pool or our custom thread pool in the Spring container.
will take the method as the dimension, and maintain the mapping relationship between the method and the thread pool in the map.
That is the step numbered ③, the executors in the code is a map:
So, what I am looking for is the logic of this place numbered ②.
This is mainly a defaultExecutor object:
This gadget is a functional programming, so if you don’t know what this gadget does, it may be a bit confusing to debug:
I suggest you make up for it, and you can get started in 10 minutes.
Eventually you will debug to this place:
org.springframework.aop.interceptor.AsyncExecutionAspectSupport#getDefaultExecutor
This code is a bit interesting, it is to get a default thread pool related Bean from the BeanFactory. The process is very simple, and the log is printed very clearly, so I won’t go into details.
But the interesting point I want to say is, I don't know if you see this code, do you see a hint of parental delegation.
They all use exceptions and handle logic in the exceptions.
The above "garbage" code directly violates two major items in the Ali development specifications:
This is good code in the source code.
In the business process, this is a violation of the norm.
So, say a digression.
It is the Ali development specification. I personally feel that it is actually a best practice for our colleagues who write business code.
But when this standard is pulled to the scope of middleware, basic components, and framework source code, there will be a little bit of inadequacy. This is a matter of opinion. I think that Ali develops a standardized idea plug-in, for me to write additions, deletions, and changes. For programmers, it is really fragrant.
Let’s not say far, let’s come back and take a look at the thread pool we got:
This will find what I want, the relevant parameters of this thread pool can be seen.
It also confirmed my previous guess:
I think the core thread number configuration is 8, the queue length should be Integer.MAX_VALUE.
However, now I get the Bean of this thread pool directly from the BeanFactory. When was this Bean injected?
Friends, isn't it easy?
I have already got the beanName of this Bean, which is applicationTaskExecutor. As long as you are a little proficient in memorizing Spring's bean-obtaining process, you know that you can put a breakpoint in this place, add debugging conditions, and go to Debug slowly. understood:
org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)
Suppose you just don’t know where to break at the above point to debug?
Let's talk about a simple and rude method. You can get the beanName, and it will come out after searching in the code.
Simple and rude effect is good:
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
Once you have found this class, just hit a breakpoint and you can start debugging.
Let's talk about the operation of a little show.
Suppose I don't even know beaName now, but I know it must be a thread pool managed by Spring.
Then I will get all the thread pools managed by Spring in the project, there is always one that I am looking for, right?
Look at the screenshot below. Isn't this bean the applicationTaskExecutor I'm looking for?
These are all wild roads and show operations, just know it, and sometimes there are multiple investigation ideas.
Return type support
Earlier we finished the first question about configuration.
Next, let’s look at another question raised earlier:
How does the source code only support void and Future?
The answer lies in this method:
org.springframework.aop.interceptor.AsyncExecutionInterceptor#invoke
The place labeled ① is actually the method we analyzed earlier to get the thread pool corresponding to the method from the map.
After getting the thread pool, come to the place labeled ②, which is to encapsulate a Callable object.
So what is encapsulated in the Callable object?
This question is not listed first, we will focus on our problem first, or there will be more and more problems.
The place marked ③, doSubmit, is the place where the task is performed, as the name knows.
org.springframework.aop.interceptor.AsyncExecutionAspectSupport#doSubmit
In fact, here is the answer I am looking for.
You see, the input parameter returnType of this method is String, which is actually the asyncSay method modified by the @Async annotation.
Believe it or not, I can show you the previous call stack. Here you can see the specific method:
How about it, I didn't lie to you.
So, now you look at what the doSubmit method does with the return type of this method.
There are a total of four branches, the first three are for judging whether it is a Future type.
The ListenableFuture and CompletableFuture are all inherited from Future.
These two classes are also mentioned in the method annotation of @Async annotation:
And our program has reached the last else, which means that the return value is not of type Future.
So what do you think it did?
After submitting the task directly to the thread pool, a null is returned.
Doesn't this cause a null pointer exception?
At this place, we also solved this problem:
How does the source code only support void and Future?
In fact, the reason is very simple, we normally use the thread pool to submit these two return types?
Submit by submit, return a Future, and encapsulate the result in the Future:
Submitted by means of execute, there is no return value:
The framework helps us achieve asynchronization through a simple annotation. No matter how fancy it is, even if it is spent, it must comply with the underlying principle of thread pool submission.
So, why does the source code only support void and Future return types?
Because the underlying thread pool only supports these two types of returns.
It’s just that it’s a little bit tricky, and it directly treats the return values of other return types as null.
Don't accept it. Who told you not to read the notes on the note?
In addition, I found that this place has a small optimization point:
When it reaches this method, the return value is clearly null.
Why use executor.submit(task)
submit the task?
Just use execute.
Difference, you ask me the difference?
Didn’t you just say that the submit method has a return value?
Although you don't use it, it will still construct a returned Future object.
However, if it was built, it was useless.
So submit it directly with execute.
Is it an optimization to generate one less Future object?
There is a saying, it's not a valuable optimization, but if you say it, you have optimized the source code of Spring, and it is enough to be used.
Next, let’s talk about the part we pressed before, what exactly is encapsulated in the place numbered ② here?
In fact, this question should have been guessed with your toes:
It’s just that the reason why I’ve come out alone is that I want to prove to you that the result returned here is the true value returned by our method.
Just judge that the type is not Future, then it will not be processed. For example, I actually returned the hi:1
string here, but it did not meet the conditions and was thrown away:
In addition, idea is still very smart, it will prompt you that the return value of this place is problematic:
Even the modification method is marked for you, you only need a little, and it will be revised for you.
As to why we have to make such a change, now we have a very clear grasp.
Knowing the reason, but also knowing the reason.
@Async annotated value
Next, let's take a look at what the value attribute of the @Async annotation does.
In fact, I mentioned it quietly before, but I took it in one sentence. This is the place:
The place where the number is ① is to get the value @Async
This value is actually the bean name. If it is not empty, the corresponding bean will be obtained from the Spring container.
Then I directly analyzed the place labeled ②.
Now let's take a look at the place marked ①.
I also reschedule a test case to verify my idea.
Anyway, the value should be the name of the Spring bean, and the bean must be a thread pool object. There is nothing to say about it.
So, I modified the Demo program to this:
Run again and run to this breakpoint, which is different from our default situation. At this time, the qualifier has a value:
The next step is to get the bean named whyThreadPool from beanFactory.
Finally, the thread pool that I took out is the thread pool I customized:
This is actually a very simple process of exploration, but there is a truth behind it.
This is the question that a classmate asked me before:
In fact, this question is quite representative. Many students think that thread pools cannot be abused, and it is good to share one for a project.
The thread pool cannot be abused, but there can be multiple custom thread pools in a project.
Divide according to your business scenario.
For example, as a simple example, a thread pool can be used in the main business process, but when a problem occurs in a certain link in the main process, suppose that an early warning message needs to be sent.
This operation of sending early warning messages can be done with another thread pool.
Can they share a thread pool?
Yes, it works.
But what will happen?
Suppose there is a problem with a certain business in the project, and it is constantly and frantically sending early warning text messages, and even the thread pool is full.
At this time, if the business of the main process and the sending of SMS use the same thread pool, what beautiful scenes will appear?
Does it go straight to the rejection strategy as soon as the task is submitted?
The subsidiary function of sending early warning text messages has caused the business to be unavailable. Is the cart before the horse?
Therefore, it is recommended to use two different thread pools, each performing its own duties.
This is actually the thread pool isolation technology that sounds very tall.
So what is going on with the annotation @Async
It's actually like this:
Then, remember the map of the mapping relationship between the maintenance method and the thread pool that we mentioned earlier?
that's it:
Now, I run the program to call the above three methods, the purpose is to put the value into this map:
do you understand?
Repeat this sentence again:
The relationship between the method and the thread pool is maintained in the method dimension.
Now, I @Async
, and I think it is still very cute. Maybe I will consider using it in the project later. After all, it is more in line with SpringBoot's annotation-based development programming philosophy.
One last word
Okay, I saw it here. Like and follow, please arrange any one. I don't mind if you arrange them all. Writing articles is tiring and requires a little positive feedback.
Knock out for readers and friends:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。