Hello, I’m Why.

I had written about thread pools, and one of my classmates looked around and realized that I hadn’t written an article about @async annotations, so he asked me:

Yes, I put my cards on the table.

The reason I don’t like this note is because I don’t use it.

I’m used to doing some asynchronous logic in a custom thread pool and have done so for years.

So if I’m leading a project, you won’t see @async annotations in the project.

Have I seen @async annotations before?

Must have seen ah, some friends like to use this note.

How cool is asynchronous development done with a single annotation.

I don’t know if the person who uses this note knows how it works, but I don’t.

Recently, I introduced a component that used this annotation in some of the methods it called.

Well, since it worked this time, let’s do some research.

First of all, this article does not cover thread pools.

Just describe the way in which I came to know this annotation that I knew nothing about before.

Have a Demo

I don’t know how anyone would do that.

But I think no matter from what Angle to start, the last will fall inside the source code.

So, I usually do a Demo first.

Demo is pretty simple, just three classes.

The first is the startup class, which is nothing to say:

Then do a service:

The syncSay method in the service is annotated @async.

Finally, make a Controller to call it, and that’s it:

The Demo is set up, you also start to make a, time-consuming more than 5 minutes, calculate I lose.

Then, start the project, invoke the interface, and view the log:

Heck, it’s not asynchronous, either, according to the thread name, right?

Why is it still a Tomcat thread?

So I ran into my first problem on the road: the @async annotation didn’t work.

Why doesn’t it work?

Why doesn’t it work?

I said I didn’t know anything about this annotation before, so how do I know?

So what happens when you have this problem?

Programming for the browser, of course!

This place, if I from the source code inside to analyze why did not take effect, must also be able to find out the reason.

However, if I were programming for the browser, it would only take me 30 seconds to look up these two information:

Failure reason:

  • 1.@SpringBootApplicationNot added to the startup class@EnableAsyncAnnotation.
  • 2. No spring-enabled proxy class. because@Transactional@AsyncAnnotation implementations are all spring-based AOP, and AOP implementations are based on the dynamic proxy pattern. It is obvious why the annotations fail, probably because the method is called by the object itself rather than the proxy object, because it is not managed by the Spring container.

Obviously, my case conforms to the first case by not adding the @enableAsync annotation.

For another reason, I am also interested, but now my priority is to build the Demo well, so I can’t be tempted by other information.

Many students with questions to query, originally check the question is why the @async annotation did not take effect, the result slowly went wrong, 15 minutes later the problem gradually evolved into the SpringBoot startup process.

Half an hour later, the website will be showing some interview must memorize the eight-part essay and so on…

What I mean by that is, look at the problem and look at the problem. In the process of checking the problem, I will be more interested in the problem caused by this problem. But, take note, don’t let the problem spread.

This truth, and with a problem to see the source code, looking at, may even their own problem is what do not know.

All right, back to that.

I put this annotation on the startup class:

Call again:

You can see that the thread name has changed, indicating that it is really good.

Now that I’ve built my Demo, I’m ready to start looking for angles.

I also know from the above log that by default there is a thread pool with the thread prefix task- to perform tasks for me.

Speaking of thread pools, I need to know the configuration of this thread pool.

So how do I find out?

Pressure is a first

In fact, the normal thinking at this time should be to look at the source code, find the corresponding injection thread pool place.

And I, is a little abnormal, I am too lazy to find the source code, I want to let it expose itself to my face.

How do we expose it?

Knowing what I know about thread pools, my first thought was to press the thread pool.

Crush it, so it can’t handle the task, so it goes into the rejection logic, which normally throws an exception, right?

So I tweaked the program a bit:

Think of a direct wave of power to perform a miracle:

The results…

It was…

Got it all. Nothing unusual?

The log is typed several lines a second, and it is typed happily:

It didn’t turn out like I expected, but I got a hint from the log.

For example, I found this taks was up to 8:

What do you mean, my friends?

Does this mean that the core thread count configuration for the thread pool I am looking for is 8?

What, you ask why I can’t be the maximum number of threads?

Is it possible?

Of course it’s possible. But I sent 10000 tasks, did not trigger the thread pool reject policy, just used up the maximum thread pool?

So this thread pool is configured with a queue length of 9992 and a maximum number of threads of 8?

Isn’t that too coincidental and unreasonable?

So I think the core thread configuration is 8 and the queue length should be integer.max_value.

To confirm my guess, I changed the request to this:

Num = 10 million.

Use JConsole to observe heap memory usage:

That’s a surge, and clicking on the “Execute GC” button doesn’t help.

Also proved from the side: tasks may be queued inside the queue, resulting in memory surge.

I don’t know what the configuration is yet, but after my black box test, I have good reason to suspect:

The default thread pool risks running out of memory.

However, it also means that I’m thwarting my attempts to expose myself by having it throw an exception.

Dui source

The previous way of thinking is not working, honest start to hate source code.

I started off with this comment:

After clicking on this note, I got a key message from a few paragraphs of English, not long:

Focus on where I drew the line.

In terms of target method signatures, any parameter types are supported.

In the signature of the target method, the input parameter is supported by any type.

One more thing: When it comes to target methods, the idea of a proxy object should immediately come to mind.

The above sentence is easy to understand, even feel a nonsense.

However, it follows a However:

However, the return type is constrained to either void or Future.

I don’t really want to do anything at all,

Return types are limited to void or Future.

What does that mean?

Why should I return a String?

WTF, the print is null! ?

So if I return an object, isn’t it easy to throw a null pointer exception?

After reading the notes on the notes, I found a second hidden hole:

If a method is decorated with an @async annotation, the return value can only be void or Future.

So void, let’s talk about the 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}

It’s temporary but I don’t think it’s temporary.

It’s a temporary worker.

So if you want to return a value, you wrap it with an AsyncResult object. This AsyncResult is a temporary worker.

Something like this:

Next, let’s look at the value attribute of the annotation:

This annotation should specify the name of a thread pool bean, which is equivalent to specifying a thread pool.

I don’t know if I understand it. I will write a method to verify it.

Well, for now, LET me put the information together.

  • I didn’t understand this annotation at all, and now I have a Demo, and when I was building the Demo I realized that in addition to@AsyncIn addition to the notes, you need to add@EnableAsyncAnnotations, such as those added to a startup class.
  • I then tested the default thread pool as a black box, and I suspected that the default core thread count was 8 and the queue length was wirelessly long. There is a risk of memory overflow.
  • By reading@AsyncThe return value can only be void or Future. Otherwise, no error will be reported if another value is returned, but the return value is null.
  • @AsyncThere is a value attribute in the annotation, which should allow you to specify a custom thread pool.

Next I’ll put the questions I want to explore in order, focusing only on @async related issues:

  • 1. What is the configuration of the default thread pool?
  • 2. How does the source code support only void and Future?
  • 3. What is the value attribute used for?

What is the specific configuration?

It was actually a very quick process for me to find the configuration.

Because the value argument of this class is too friendly:

Five calls, four of which are comments.

The only valid call is at this point:

org.springframework.scheduling.annotation.AnnotationAsyncExecutionInterceptor#getExecutorQualifier

After making the call, sure enough, we get to the breakpoint:

Debugging down the breakpoint leads to this:

org.springframework.aop.interceptor.AsyncExecutionAspectSupport#determineAsyncExecutor

The code structure is very clear.

Where (1) is the value of the @async annotation on the corresponding method. This value is essentially the bean name, or if not empty, the corresponding bean is retrieved from the Spring container.

If value has no value, which is the case in our Demo, it’s going to go to number 2.

This is the default thread pool I’m looking for.

Finally, either the default thread pool or our custom thread pool in the Spring container.

Both maintain the mapping between methods and thread pools in the map, using the method dimension.

* / * the executors in the following code are true:

So, what I’m looking for is the logic of this number 2.

This is mainly a defaultExecutor object:

This is functional programming, so if you don’t know what it does, debugging can be a bit confusing:

I suggest you catch up. You can get up to speed in 10 minutes.

Eventually you debug to this place:

org.springframework.aop.interceptor.AsyncExecutionAspectSupport#getDefaultExecutor

This code gets a default thread pool-related Bean from the BeanFactory. The process is simple and the log is printed clearly, so I won’t go into details.

But the interesting thing I want to say is, I don’t know if you see anything in this code that smells like parental delegation.

It’s all about using exceptions, handling logic in exceptions.

On the above “garbage” code, directly violated the ali development specification of the two major:

This is good code in the source code.

In a business process, this is a specification violation.

So, an aside.

Ali development specification is actually a best practice for my colleagues who write business code.

But when pull the scale to the middleware framework, basic components, the scope of the source code, can appear a little because the symptoms of this thing, I do think ali development idea plug-in, for I wrote this add and check of programmers, is really sweet.

Without further ado, let’s go back to the thread pool obtained:

Now that I’ve found what I’m looking for, I can see the parameters of the thread pool.

And it confirms what I suspected:

I think the core thread configuration is 8 and the queue length should be integer.max_value.

But now that I got the thread pool Bean directly from the BeanFactory, when was the Bean injected?

My friends, isn’t that easy?

I already have the beanName of this Bean, which is applicationTaskExecutor. If you are familiar with Spring’s beanName process, you will know where to put breakpoints and debug conditions. Debug slowly to know:

org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)

What if you just don’t know where to break in to debug?

Here’s another simple and rude way to do it: you’ve got beanName, you can just search the code and find it.

Simple and rough effect is good:

org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration

Now that you’ve found the class, just hit a breakpoint and start debugging.

Let’s do a little bit more sleazy.

Let’s say I don’t even know about beaName, but I do know it’s a spring-managed thread pool.

So I get all the spring-managed thread pools in the project, and one of them has to be the one I’m looking for, right?

This bean is the applicationTaskExecutor.

This is some wild way, SAO operation, know it is good, sometimes multiple investigation ideas.

Return type support

Earlier we finished the first configuration question.

Next, let’s look at another question that was raised earlier:

How does the source code support only void and Future?

The answer lies in this method:

org.springframework.aop.interceptor.AsyncExecutionInterceptor#invoke

The thread pool for method (1) is the thread pool for method (1).

Once you get to the thread pool, you wrap a Callable object at number 2.

So what is wrapped inside a Callable object?

This problem first press not table, we first firmly around our problem to go down, otherwise the problem will be more and more.

The place marked ③, doSubmit, by name, is the place to execute the task.

org.springframework.aop.interceptor.AsyncExecutionAspectSupport#doSubmit

This is actually the answer I was looking for.

See, the returnType of this method is a String, which is an asyncSay method decorated with the @async annotation.

If you don’t believe me, I can take you through the previous call stack. Here you can see the specific method:

What do you think? I’m telling you.

So now you see what the doSubmit method does with the method return type.

There are four branches, and the first three are all Future types.

The ListenableFuture and CompletableFuture inherit from the Future.

These two classes are also mentioned in the @async method annotation:

And our program goes to the last else, which means that the return value is not of type Future.

So what did you see it do?

After submitting the task directly to the thread pool, a NULL is returned.

Can’t this throw a null pointer exception?

At this place, we also solved this problem:

How does the source code support only void and Future?

In fact, the truth is very simple, we normally use thread pool commits are not these two return types?

Submit to return a Future and encapsulate the result in the Future:

Execute () with no return value:

The framework makes it asynchronous with a simple annotation, and even when it does, it has to follow the underlying principles 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 a bit of a trick, though, by treating all other return types as null.

Don’t be a rebel. You read the notes.

In addition, I found a small optimization point in this place:

By the time it reaches this method, the return value is already explicitly null.

Why use executor.submit(task) to submit a task?

Just use execute.

Difference? You ask me the difference?

As I just said, the Submit method returns a value.

You don’t have to, but it still builds a Future object that returns.

But when you build it, it doesn’t work.

So just commit with execute.

Is it optimization to generate one less Future object?

It is not a valuable optimization, but it is optimized Spring source code, install force enough.

Next, let’s talk about the part that we clicked on earlier. What exactly is encapsulated in the part numbered ② here?

In fact, this question should also be guessed by toes:

But the reason I broke it out is because I want to show you that the result returned here is the actual value returned by our method.

If the type is not Future, it will not be processed. For example, if the type is not Future, it will return hi:1.

In addition, IDEA is smart enough to tell you that there is a problem with the return value:

Even the modification method is marked for you, you only need a little bit, it will be changed for you.

Now we have a pretty good idea why we changed it.

Know what it is and why.

@async Value of the annotation

Now let’s look at what the value property of the @async annotation does.

In fact, I have quietly mentioned in the front, just one sentence to bring over, is this place:

Get the value of the @async annotation on the corresponding method. This value is essentially the bean name, or if not empty, the corresponding bean is retrieved from the Spring container.

And then I’m going to go straight to 2.

Now let’s go back to this one.

I also rearranged a test case to validate my idea.

The value should be the name of the Spring bean, and the bean must bea thread pool object.

So, I changed the Demo program to look like this:

Run again to the breakpoint, where it is different from our default, and qualifier has a value:

The next step is to go to the beanFactory and get the bean named whyThreadPool.

Finally, the thread pool is my custom thread pool:

This is actually a very simple process of exploration, but there is a truth behind it.

I was asked this question by some students earlier:

In fact, this problem is quite representative, many students think that thread pool can not be abused, a project to share a good.

Thread pools cannot be abused, but it is possible to have multiple custom thread pools within a project.

Divide by your business scenario.

For a simple example, a thread pool can be used on the main business process, but when something goes wrong in the main process, let’s say you need to send an alert message.

The operation of sending an alert message can be done with another thread pool.

Can they share a thread pool?

Yes, yes.

But what could go wrong?

Let’s say there’s a problem with a business in the project, and it’s sending out warning messages like crazy, or even filling up the thread pool.

What would be the beautiful scenario if the main flow was in the same thread pool as the one sending the SMS?

Did you go straight into the rejection policy as soon as you submitted the task?

Early warning SMS send this ancillary function, resulting in business can not be, put the cart before the horse, right?

Therefore, it is recommended to use two different thread pools, each doing its job.

This is actually a high-sounding thread pool isolation technique.

So what happens when you drop on the @async annotation?

Here’s what it looks like:

Then, remember that map we mentioned earlier that maintains the mapping between methods and thread pools?

Is it:

Now, I’ll run the program and call the above three methods in order to put values into the map:

Does that make sense?

Repeat the sentence one more time:

Maintain the relationship between methods and thread pools in the method dimension.

Now that I know a little bit about the @async annotation, I think it’s still pretty cute. Maybe I’ll consider using it later in the project. After all, it is more in line with SpringBoot’s annotation-based development philosophy.

One last word

Ok, see here, like, follow whatever arrangement, if you arrange all of them, I don’t mind. Writing is tiring and needs some positive feedback.

Here’s one for readers: