一、背景
对于一些的耗时的且与处理结果业务不是紧密关联的,我们采用异步调用的方式处理。一般我们会手动创建一个线程池,来执行这个耗时的异步任务。其实spring 已经提供了一个注解来帮我们干了这件事了
二、使用方式
使用方式就是很简单了
1、在启动类中加入@EnableAsync 是异步调用 @Async注解生效
2、在需要异步执行的方法上加上@Async,也可以在类上面加,表示该类中的所有的方法都是需要异步执行的
注意点:
1、默认情况情况下使用的是这个SimpleAsyncTaskExecutor 线程池,这个并不是真正意义的线程池,线程是不重用的,每个任务来都会创建一个新的线程。有可能导致OOM问题,所以建议自定义线程池,通过实现AsyncConfigurer 类,重写getAsyncExecutor 方法。代码如下:
@Slf4j
@Component
public class MyAsyncConfigurer implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ExecutorService service = Executors.newFixedThreadPool(10);
return service;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new MyAsyncExceptionHandler();
}
class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
log.info("Exception message - " + throwable.getMessage());
log.info("Method name - " + method.getName());
for (Object param : objects) {
log.info("Parameter value - " + param);
}
}
}
}
2、调用的异步方法不能是同一个类的方法。原因是@Async注解本质上是使用的动态代理,spring在启动扫描时,将含有AOP注解的类和对象替换成代理类,而同类调用时,还是调用的是对象本身,并不是代理类。 所以会导致@Async失效。同样的问题@Transaction、@cache 也有。
解决这问题可以将异步的方法放到一个单独的类中,这个类加上@compent 交给spring 管理。或者我们可以通过spring的上下文来获取代理对象。详细代码:
@RestController
@RequestMapping("/api")
@Slf4j
public class ApiController {
@Resource
private ApplicationContext applicationContext;
@RequestMapping("/asynCall")
public Object asynCall() {
try {
ApiController apiController = applicationContext.getBean(ApiController.class);
for (int i= 0;i<10;i++) {
Future future = apiController.testAsynTask(i);
}
return "success";
} catch (Exception e) {
return "error";
}
}
@Async
public Future testAsynTask(int i ) throws InterruptedException {
Thread.sleep(10000);
System.out.println("异步任务执行完成..,"+i+","+Thread.currentThread().getName());
Future future = new AsyncResult("ok");
return future;
}}