Why is it not recommended to use the @Async annotation to create threads?

1 Introduction

Insert image description here
A long time ago, I had a painful memory. The feeling of being driven by a malfunction remained in my mind for a long time.

There is no other reason. Some friends have enabled the violent use mode of the thread pool. That’s right, it’s the article below.

Fatal glitch! Exploded investors!

I need to restate it briefly. The main reason is that developers create a separate thread pool for each method call. In this case, if the number of requests increases, the pressure on the entire operating system will be exhausted, and eventually all businesses will be unable to respond.
Insert image description hereI always thought this was a very sporadic low-level error that occurred very rarely. But as more and more such failures occur, xjjdog realizes that this is a common phenomenon.

The purpose of asynchronous performance optimization is to make the overall business unavailable, which is a very slap in the face.

2 Spring’s asynchronous code

As the leading sub-framework in Java, Spring's over-encapsulated API is deeply loved by developers. According to the logic of semantic programming, as long as certain keywords are acceptable at the language level, we can add them. For example, @Async annotation.

I can never figure out what gives developers the courage to add this @Async annotation, because when it comes to multi-threading, even if you create threads yourself, you are still in awe, lest it disturb the peace of the operating system. Can a black box like @Async really be used so smoothly?

We might as well debug the code and let the bullets fly for a while.

First, generate a small project, and then add the necessary annotations to the main class. Well, don't forget this part, otherwise the comments you add later will be of no use.

@SpringBootApplication
@EnableAsync
public class DemoApplication {
    
    

Create a method annotated with @Async.

@Component
public class AsyncService {
    
    
    @Async
    public void async(){
    
    
        try {
    
    
            Thread.sleep(1000);
            System.out.println(Thread.currentThread());
        }catch (Exception ex){
    
    
            ex.printStackTrace();
        }
    }
}

Then, make a corresponding test interface, and this async method will be called when accessing.

@ResponseBody
@GetMapping("test")
public void test(){
    
    
    service.async();
}

When accessing, just set a breakpoint to obtain the thread pool for executing asynchronous threads.
Insert image description here
It can be seen that the asynchronous task uses a thread pool with corePoolSize=8, and the blocking queue uses the unbounded queue LinkedBlockingQueue. Once such a combination is adopted, the maximum number of threads will be useless, because all tasks exceeding 8 threads will be placed in the unbounded queue. The following code becomes a decoration.

throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, var4);

If your visit volume is very large, these tasks will all pile up in the LinkedBlockingQueue. If the situation is better, the execution of these tasks will become very delayed; if the situation is worse, too many tasks will directly cause memory overflow OOM!

You may say, I can specify another ThreadPoolExceute myself, and then use the @Async annotation to declare it. The students who said this must have relatively strong abilities, or have reviewed less code, and have not been baptized by pig teammates.

3 SpringBoot saves you

SpringBoot is a good thing.

In TaskExecutionAutoConfiguration, the default Executor is provided by generating the Bean of ThreadPoolTaskExecutor.

@ConditionalOnMissingBean({
    
    Executor.class})
public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
    
    
    return builder.build();
}

That's what we said above. Without the help of SpringBoot, Spring will use SimpleAsyncTaskExecutor by default.

参见org.springframework.aop.interceptor.AsyncExecutionInterceptor。

@Override
@Nullable
protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
    
    
 Executor defaultExecutor = super.getDefaultExecutor(beanFactory);
 return (defaultExecutor != null ? defaultExecutor : new SimpleAsyncTaskExecutor());
}

This is what Spring is doing.

The SimpleAsyncTaskExecutor class is very poorly designed, because every time it is executed, a separate thread is created, and there is no shared thread pool at all. For example, if your TPS is 1000 and the task is executed asynchronously, you will generate 1000 threads per second!

This is obviously a rhythm that wants to exhaust the operating system.

protected void doExecute(Runnable task) {
    
    
 Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));
 thread.start();
}

4 ending

At a glance, this method of using new threads will be very scary. But taking Spring itself as an example, there are many places that refer to the SimpleAsyncTaskExecutor class, including the more popular AsyncRestTemplate.
Insert image description here
This exposes a lot of risks, especially seeing redis in these lists. The design of this class makes the execution of tasks very uncontrollable.

Looking at this API, I feel that Spring has entered a state of design confusion.

The hidden bugs in this thing may be even deeper! For example, the org.springframework.context.event.EventListener annotation is used to implement the so-called event-driven model of DDD. Many frameworks directly set SimpleAsyncTaskExecutor, so just wait for death.

Hurry up and add SimpleAsyncTaskExecutor to your API blacklist or pit list!

Is it that difficult to create a thread? Need to use Spring-created threads? Sometimes I really can't figure out what the purpose of exposing such an interface is.

We haven't even figured out the native thread pool yet, but you've also included a layer of it, which makes it easier for us to take the blame!

Guess you like

Origin blog.csdn.net/qq_37284798/article/details/132469040