目录
Spring中异步方法的使用
1. 异步方法描述
异步方法,顾名思义就是调用后无须等待它的执行,而继续往下执行;@Async是Spring的一个注解,在Spring Boot中,我们只需要使用@Async注解就能简单的将原来的同步函数变为异步函数。
对于比较耗时的操作,我们可以抽取成异步方法来让主线程稳定快速继续执行,对于异步方法的执行结果可根据自己的要求是否需要在主线程处理;
2. 异步方法的实现步骤
在springboot应用我们可以使用简单的两个注解即可开启并使用异步方法;
- 创建一个普通的Service类,并有@Service修饰,表示这个服务类交给Spring管理;
- 在Service方法里定义一个普通的方法,使用@Async修饰;表示这是一个异步方法;
- 在引导类中添加@EnableAsync注解,使应用开启对异步方法的支持;
3. 实测一下
3.1 定义个Service类
@Slf4j
@Service
public class MyAsyncSevice {
@Async
public void myAsyncMehtod(){
log.info("---> enter aysnc method");
try {
Thread.sleep(5000);
int i = 1/0;
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("---> end of async method");
}
}
3.2 定义个测试Controlle
@Slf4j
@RestController
public class AsyncContorller {
// 注入异步方法所在的类
@Autowired
MyAsyncSevice myAsyncSevice;
@GetMapping("/async-test")
public String asyncTest(){
log.info("--> enter controller");
myAsyncSevice.myAsyncMehtod();
log.info("--> end of controller");
return "hello";
}
}
3.3 在启动类上启用异步
@EnableAsync
@SpringBootApplication
public class JsrDemoApplication {
public static void main(String[] args) {
SpringApplication.run(JsrDemoApplication.class, args);
}
}
3.4 测试
可以看到输出,controlle中调用了异步方法后继续执行,即使异步方法报错也不会controller的执行;
curl -X GET -H "http://localhost:8080/async-test"
2021-08-20 17:42:50.810 --- [nio-8080-exec-1] AsyncContorller : --> enter controller
2021-08-20 17:42:50.813 --- [nio-8080-exec-1] AsyncContorller : --> end of controller
2021-08-20 17:42:50.821 --- [ task-1] MyAsyncSevice : ---> enter aysnc method
2021-08-20 17:42:55.832 --- [ task-1] MyAsyncSevice : ---> end of async method
4. 补充
4.1 Executor线程池
上面的测试,我们并没有创建新的线程和线程池,如果我们不配置线程池的Bean,Spring会自动创建SimpleAsyncTaskExecutor,并使用它来执行异步方法。定义线程池bean可参考如下:
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(3);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("MyThreadPool");
executor.initialize();
return executor;
}
4.2 异步返回Futrue
4.2.1 这样改造我们的异步方法,即返回一个Futrue类型的结果;
@Async
public Future myAsyncMehtod(){
log.info("---> enter aysnc method");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("---> end of async method");
return new AsyncResult("I am async method result");
}
4.2.2 Contorller也改造下
使用了Futrue.get方法,controller主线程就会等待异步方法的执行结束,或等待超时后才会结束。
@GetMapping("/async-test")
public String asyncTest(){
log.info("--> enter controller");
Future ft = myAsyncSevice.myAsyncMehtod();
try {
ft.get(1000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
log.info("--> end of controller");
return "hello";
}
4.3 其他注意事项
- 在同一个类中的方法调用,添加@async注解是失效的。原因是当你在同一个类中的时候,方法调用是在类中执行的,spring无法截获这个方法调用,也就不会在代理类里执行。
- 可能会导致循环依赖,spring本身会解决循环依赖,但是因为@Async使用代理模式,spring在检查第二级缓存和原始对象是否相等时发现不相等,会抛出异常。
- 无法获取请求上下文。