前言
Spring中用@Async
注解标记的方法,称为异步方法,其实就相当于我们自己在当前方法里面:new Thread(()-> System.out.println("hello world !"))
。
使用@Async
注解的时候一定要在类上加@EnableAsync
(注解形式),@EnableAsync
注解的意思是开启支持异步,就是开启支持多线程的意思。可以标注在方法、类上。
@Scheduled、@EnableScheduler与@Async
、@EnableAsync
类似。
使用
spring的xml配置如下:
<!-- 使@Async和@Scheduled生效,相当于注解形式的@EnableAsync+@EnableScheduling -->
<task:annotation-driven scheduler="taskScheduler" executor="taskExecutor"/>
<!-- 调度线程池配置(调度被@Scheduled注解的方法,若方法上没有@Async注解就立即在本scheduler线程执行该方法,若有就在executor线程池中取一个线程执行该方法),若不配置,多任务下会有阻塞问题,因为默认是1,所有声明的任务都是串行调度的 -->
<task:scheduler id="taskScheduler" pool-size="6"/>
<task:scheduler id="testScheduler" pool-size="6" />
<!-- 执行线程池配置(执行被@Asyn注解的方法),若不配置,某个任务到了第N次执行的时间但是第N-1还未执行完时会有阻塞问题(即使设置了fixRate),因为默认是1,任务的多次执行是串行执行的 -->
<task:executor id="taskExecutor" pool-size="2" />
<task:executor id="testExecutor" pool-size="10" />
对于task:scheduler
- 如果没有配置task:scheduler,则task:annotation-driven无需指定scheduler,且默认是TaskScheduler,线程名称:[pool-1-thread-1]
- 如果配置了一个task:scheduler,则task:annotation-driven无需指定scheduler,且默认是配置的这个scheduler(即会自动发现已配置的scheduler),线程名称:[{scheduler名称}-1]。
- 如果配置了多个task:scheduler,且task:annotation-driven未指定scheduler,则task:annotation-driven会自动发现多个scheduler导致报错说必须指定一个scheduler。
对于task:executor
- 如果没有配置task:executor,则task:annotation-driven无需指定executor,且默认是
SimpleAsyncTaskExecutor
(容易出现内存溢出,参考@Async默认线程池导致OOM问题),线程名称:[SimpleAsyncTaskExecutor-1] - 如果配置了一个或多个task:executor,且task:annotation-driven未指定executor,且默认是
SimpleAsyncTaskExecutor
(即不会自动发现已配置的executor),若指定了就是指定的那一个,线程名称:[{executor名称}-1]。
按@Async
注解使用的基本方法:
- 在方法上添加
@Async
注解,且必须是public方法; - 所使用的
@Async
注解方法的类对象应该是Spring容器管理的bean; - 调用异步方法的类上需要配置上注解
@EnableAsync
(注解形式)
异步不生效问题
不要同类方法调用
异步方法所在类的另一个方法调用这个异步方法,异步不会生效!
原因是如果调用方和被调用方是在同一个类中,是无法产生切面的,@Async没有被Spring容器管理。
重复扫描
component-scan扫描或注解扫描时,要注意过滤,避免重复实例化,因为存在覆盖问题,@Async
就失效了
没有定义线程池
如果不自定义异步方法的线程池默认使用SimpleAsyncTaskExecutor
。SimpleAsyncTaskExecutor
:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。并发大的时候会产生严重的性能问题。
定义通用线程池
@EnableAsync
@Configuration
public class ThreadTestConfig {
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
pool.setCorePoolSize(5); //线程池活跃的线程数
pool.setMaxPoolSize(10); //线程池最大活跃的线程数
pool.setWaitForTasksToCompleteOnShutdown(true);
pool.setThreadNamePrefix("lalala");
return pool;
}
}
@Async
默认使用Bean Name
为taskExecutor
的线程池。也可以根据Bean Name
指定特定线程池
@Async("taskExecuters2")
public void asyncMethod() {
sleep(10);
System.out.println("async");
}
源码分析
参考另一篇基于SpringBoot分析的文章【SpringBoot】@Async、AsyncConfigurer源码解析