本文主要讲解 @Async 的基本使用及和 AsyncConfigurer 接口的关系,对于线程池的作用、线程池的参数(核心线程、最大线程......)及运行原理,这里不再过多赘述。
先说一些基础知识,后面再看些示例。
1、在方法上使用该 @Async 注解,申明该方法是一个异步任务。
2、在类上使用该 @Async 注解,申明该类中的所有方法都是异步任务。
3、使用此注解的方法的类对象,必须是spring管理下的bean对象。
4、要想使用异步任务,需要在主启动类或者@configure注解类上开启异步配置,即,配置上 @EnableAsync 注解。对于Spring注解 @Async,Spring是以配置文件的形式来开启 @Async,而SpringBoot则是以注解 @EnableAsync的方式开启。
5、异步方法使用注解 @Async 的返回值只能为 void 或者 Future。
6、@Async 失效处理:
- 异步方法使用static修饰。不要定义为static类型,这样异步调用不会生效。
- 方法必须是public方法。
- 标注@Async注解的方法和调用的方法一定不能在同一个类下,也就是说,方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的,因为@Transactional和@Async注解的实现都是基于Spring的AOP,而AOP的实现是基于动态代理模式实现的。那么注解失效的原因就很明显了,有可能因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器。
- 使用@Async注解的方法的所在类,一定要交给spring容器来管理。用@Component注解(或其他注解)。
7、若没有通过实现 AsyncConfigurer 接口来自定义线程池,则使用的是Spring默认的线程池 SimpleAsyncTaskExecutor。
8、实现 AsyncConfigurer 接口,主要作用:
- 自定义线程池。
- 异常处理。
9、实现 AsyncConfigurer 接口并重写getAsyncExecutor()方法,@Async 默认使用的是重写方法中的线程池。
10、@Async("xxx"),可以使用指定线程池xxx。
11、若有两个实现类实现 AsyncConfigurer 接口,则启动报错。所以说还是比较局限。
12、@Async("xxx")指定使用了其他线程池,出现异常,还是使用的实现 AsyncConfigurer 接口的实现类中的异常处理方法(如果重写了)。也是比较局限(当其他业务不想使用此处理方式时)。
13、异步任务的事务问题:@Async注解的方法由于是异步执行的,在其进行数据库的操作,将无法控制事务管理。 解决办法:可以把@Transactional注解放到内部的需要进行事务的方法上。
14、被 @Async 的方法在独立线程调用,不能被 @ControllerAdvice 全局异常处理器捕获。(这个未验证,有兴趣的可以自行验证)。
下面看些示例
示例1:没有实现AsyncConfigurer情况下,使用@Async
/**
* 注意这个多线程方法的类要加@Component注解,交给spring容器管理
*/
@Slf4j
@Component
public class AsyncMethods {
@Async
public void test1(){
try {
//暂停一会线程
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
log.info(Thread.currentThread().getName());
}
}
直接在启动类中测试
@EnableAsync
@SpringBootApplication
public class ThreadPoolMainApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(ThreadPoolMainApplication.class, args);
AsyncMethods asyncMethods = context.getBean("asyncMethods", AsyncMethods.class);
for (int i = 0; i < 5; i++) {
asyncMethods.test1();
}
}
}
结果:使用的是Spring默认的线程池 SimpleAsyncTaskExecutor
示例2:实现AsyncConfigurer情况下,使用@Async
@Slf4j
@Configuration
public class AsyncConfig implements AsyncConfigurer {
/**
* 获取线程池
*
* @return
*/
@Override
public Executor getAsyncExecutor() {
return new ThreadPoolExecutor(4,
5,
5,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10),
new NamedThreadFactory("asyn"),
// new ThreadPoolExecutor.CallerRunsPolicy()
new ThreadPoolExecutor.AbortPolicy()
);
}
/**
* 异常处理
*
* @return
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (throwable, method, objects) -> {
//可以做一些其他的业务处理,比如:异常记录日志
log.error("class: {}", method.getDeclaringClass().getName());
log.error("method:{}", method.getName());
log.error("type:{}", throwable.getClass().getName());
log.error("exception:{}", throwable.getMessage());
};
}
}
结果:使用的是自定义的线程池。
示例3:实现AsyncConfigurer,没有重写异常处理方法
@Slf4j
@Component
public class AsyncMethods {
@Async
public void test1(){
try {
//暂停一会线程
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
//制造异常
System.out.println(10/0);
log.info(Thread.currentThread().getName());
}
}
结果:
示例4:实现AsyncConfigurer,重写异常处理方法
异常处理的代码 示例2 中已经给出,直接看结果:
示例5:使用@Async("xxx"),指定使用其他线程池
自定义线程池:
@Slf4j
@Configuration
public class ThreadPoolConfig {
@Bean
public ThreadPoolExecutor myThreadPool() {
return new ThreadPoolExecutor(4,
5,
60,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10),
new NamedThreadFactory("hard-study"),
new ThreadPoolExecutor.AbortPolicy()
);
}
}
指定线程池:@Async("myThreadPool")
@Async("myThreadPool")
public void test2(){
try {
//暂停一会线程
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
log.info(Thread.currentThread().getName());
}
直接启动类测试,调用test2方法,其余代码不贴了。
结果:使用的是指定的线程池。
示例6:创建2个实现AsyncConfigurer接口的类,启动报错
代码省略,看结果:
示例7:若异步方法需要返回值,使用Future<String>、new AsyncResult<>()
@Async
public Future<String> test3(){
log.info(Thread.currentThread().getName());
return new AsyncResult<>("任务三完成");
}