一、简介
@Async注解是spring中用来标注此方法是通过另外一个线程异步调用的,调用者将在调用时立即返回,方法的实际执行将提交给Spring TaskExecutor的任务中,由指定的线程池中的线程执行。
- @Async标注在类上时,表示该类的所有方法都是异步方法。
- @Async注解的方法一定要通过依赖注入调用(因为要通过代理对象调用),不能直接通过this对象调用,否则不生效。
二、使用示例
需要开启异步调用,在springBoot启动类上配置@EnableAsync注解
publicclassSpringbootJenkinsApplication { publicstaticvoidmain(String[] args) { SpringApplication.run(SpringbootJenkinsApplication.class, args); } }
在service层使用@Async注解
@Service
publicclassAsyncService { // 无返回值的异步方法publicvoidasyncTest1() { StringtName=Thread.currentThread().getName(); System.out.println("current thread name : "+tName); System.out.println("noReturnMethod end"); } // 有返回值的异步方法publicFuture<String>asyncTest2() { StringtName=Thread.currentThread().getName(); System.out.println("current thread name : "+tName); returnnewAsyncResult<>("hello,world"); } }
创建调用异步方法的类
publicclassAsyncController { AsyncServiceasyncService; // 无返回值"/test1") (publicStringnoReturn() { asyncService.asyncTest1(); return"success"; } // 有返回值"/test2") (publicStringwithReturn() { Future<String>future=asyncService.asyncTest2(); try { Stringres=future.get();// 阻塞获取返回值System.out.println("res = "+res); } catch (InterruptedException|ExecutionExceptione) { e.printStackTrace(); } return"success"; } }
在spring源码中对@EnableAsync做了以下解释:
默认情况下,Spring 将在上下文中搜索类型的唯一org.springframework.core.task.TaskExecutor bean,或者名为“taskExecutor”的java.util.concurrent.Executor bean。如果两者都没找到,则将使用org.springframework.core.task.SimpleAsyncTaskExecutor来处理异步方法调用。此外,具有void返回类型的带注解的方法不能将任何异常传输回调用者。默认情况下,仅记录此类未捕获的异常。
而SimpleAsyncTaskExecutor线程池去执行@Async标注的异步方法,由于该线程池不会重用线程,所以项目中推荐使用自定义的线程池。
默认线程池弊端
在线程池应用中,参考阿里巴巴java开发规范:线程池不允许使用Executors去创建,不允许使用系统默认的线程池,推荐通过ThreadPoolExecutor的方式,这样的处理方式让开发的工程师更加明确线程池的运行规则,规避资源耗尽的风险。Executors各个方法的弊端:
- newFixedThreadPool和newSingleThreadExecutor:主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
- newCachedThreadPool和newScheduledThreadPool:主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
三、自定义线程池三种方式
(一)实现接口
开发者可以通过实现AsyncConfigurer接口来自定义自己的线程池,以下是官方文档给的例子:
publicclassAppConfigimplementsAsyncConfigurer { publicExecutorgetAsyncExecutor() { ThreadPoolTaskExecutorexecutor=newThreadPoolTaskExecutor(); executor.setCorePoolSize(7); executor.setMaxPoolSize(42); executor.setQueueCapacity(11); executor.setThreadNamePrefix("MyExecutor-"); executor.initialize(); returnexecutor; } publicAsyncUncaughtExceptionHandlergetAsyncUncaughtExceptionHandler() { returnnewMyAsyncUncaughtExceptionHandler(); }
(二)继承类
通过继承AsyncConfigurerSupport类
publicclassAppConfigextendAsyncConfigurerSupport { publicExecutorgetAsyncExecutor() { ThreadPoolTaskExecutorexecutor=newThreadPoolTaskExecutor(); executor.setCorePoolSize(7); executor.setMaxPoolSize(42); executor.setQueueCapacity(11); executor.setThreadNamePrefix("MyExecutor-"); executor.initialize(); returnexecutor; } publicAsyncUncaughtExceptionHandlergetAsyncUncaughtExceptionHandler() { returnnewMyAsyncUncaughtExceptionHandler(); }
(三)配置自定义的TaskExecutor
publicclassAppConfig { name=AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME) (publicExecutortaskExecutor() { ThreadPoolTaskExecutorexecutor=newThreadPoolTaskExecutor(); executor.setCorePoolSize(7); executor.setMaxPoolSize(42); executor.setQueueCapacity(11); executor.setThreadNamePrefix("MyExecutor-"); executor.initialize(); returnexecutor; } }
(四)异常处理
当方法是带Future返回值的时候,Future.get()方法会抛出异常,所以异常捕获是没问题的。但是当方法是不带返回值的时候,那么此时主线程就不能捕获到异常,需要额外的配置来处理异常,可以有下面两种方式。
1、通过try-catch处理异常
直接在异步方法中使用try-catch来处理抛出的异常。这个方法也可以用于带Future返回值的异步方法。
2、通过实现AsyncUncaughtExceptionHandler接口
publicclassSpringAsyncConfigimplementsAsyncConfigurer { publicExecutorgetAsyncExecutor() { // 省略自定义线程池的代码 } // 自定义异常处理publicAsyncUncaughtExceptionHandlergetAsyncUncaughtExceptionHandler() { returnnewAsyncUncaughtExceptionHandler() { publicvoidhandleUncaughtException(Throwablethrowable, Methodmethod, Object... objects) { System.out.println(method.getName() +"发生异常!异常原因:"+throwable.getMessage() ); } }; } }