SpringBoot异步注释@Async的并发陷阱

22-09-23 banq

在 Java 并发编程中实现异步函数之前,一般需要使用线程或线程池。
线程池的底层也使用线程。
要实现一个线程,要么继承Thread类,要么实现Runnable接口,然后在run方法中编写具体的业务逻辑代码。

Spring开发者为了简化这种异步操作,已经帮我们封装了异步功能。
Spring提供了@Async注解,通过它我们可以开启异步功能,使用起来非常方便。

1、将@EnableAsync注解添加到SpringBoot

.
@EnableAsync
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}


2 、在需要进行异步调用的业务方法中添加注解@Async。

@Service
public class CategoryService {

     @Async
     public void add(Category category) {
        // add category
     }
}


3、在方法中调用该业务controller方法。

@RestController
@RequestMapping("/category")
public class CategoryController {

     @Autowired
     private CategoryService categoryService;
  
     @PostMapping("/add")
     public void add(@RequestBody category) {
        categoryService.add(category);
     }
}


这将启用异步功能。

这简单吗?
但是有一个坏消息:使用 @Async注解启用的异步函数会调用该类AsyncExecutionAspectSupport的doSubmit方法。
其中会执行:

executor.submit(task);


最终调用下面doExecute方法:

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


如果使用@Async注解启用异步功能,这将导致问题,默认情况下,每次都会创建一个新线程。
在高并发场景下,可能会产生大量线程,导致OOM问题。
因此:

@Async在开启异步功能时不要忘记定义线程池。