本文主要介绍如何使用 Spring 框架提供的异步调用注解 @Async, 异步线程池配置, 异常捕获处理.
开启 @Async 注解支持
使用 @Async 注解的之前, 必须在项目中启动时调用 @EnableAsync 注解. 比如通过定义一个 JavaConfig 文件:
- @Configuration
- @EnableAsync
- public class AsyncConfig {
- }
异步调用
使用 @Async 异步执行无返回值的任务
定义一个任务类 AsyncTask, 包含两个执行耗时任务的方法 task1(),task2(), 在两个方法上添加 @Async
- @Service
- @Slf4j
- public class AsyncTask {
- @Async
- public void task1() {
- log.info("task1 start");
- }
- @Async
- public void task2() {
- log.info("task2 start");
- }
- }
定义测试类, 串行调用 AsyncTask.task1() 和 AsyncTask.task2()
- @RunWith(SpringRunner.class)
- @SpringBootTest
- @Slf4j
- public class AsyncTaskTest {
- @Autowired
- private AsyncTask asyncTask;
- @Test
- public void taskTest() {
- log.info("taskTest start");
- asyncTask.task1();
- asyncTask.task2();
- log.info("taskTest end");
- }
- }
从运行结果中看, task1 和 task2 分别在两个不同的线程中执行:
- INFO [15:18:29.182][main][com.breezek.demo.common.AsyncTaskTest][25]:taskTest start
- INFO [15:18:29.188][main][com.breezek.demo.common.AsyncTaskTest][29]:taskTest end
- INFO [15:18:29.192][task-1][com.breezek.demo.common.AsyncTask][29]:task2 start
- INFO [15:18:29.192][task-2][com.breezek.demo.common.AsyncTask][24]:task1 start
异步回调
使用 @Async 异步执行有返回值的任务, 并获取任务执行结果.
定义 AsyncTask 类, 创建两个带返回值的异步方法, 返回值类型为 Future,task1 执行时间 5s,task2 执行时间 10s, 在两个方法上添加 @Async
- @Service
- @Slf4j
- public class AsyncTask {
- @Async
- public Future<String> task1() throws InterruptedException {
- log.info("task1 start");
- Thread.sleep(5000L);
- log.info("task1 end");
- return new AsyncResult<>("task1 result");
- }
- @Async
- public Future<Integer> task2() throws InterruptedException {
- Integer abc = 1;
- log.info("task2 start");
- Thread.sleep(10000L);
- log.info("task2 end");
- return new AsyncResult<>(abc);
- }
- }
定义测试类, 分别调用 task1,task2, 并等待 task1 和 task2 执行完毕
- @RunWith(SpringRunner.class)
- @SpringBootTest
- @Slf4j
- public class AsyncTaskTest {
- @Autowired
- private AsyncTask asyncTask;
- @Test
- public void taskTest() throws InterruptedException {
- log.info("taskTest start");
- Future<String> task1Future = asyncTask.task1();
- Future<Integer> task2Future = asyncTask.task2();
- // do something
- for (int i = 0; i < 1000; i++) {
- }
- while (!task1Future.isDone() || !task2Future.isDone()) {
- }
- log.info("taskTest end");
- }
- }
运行结果:
- INFO [17:54:24.554][main][com.breezek.demo.common.AsyncTaskTest][28]:taskTest start
- INFO [17:54:24.566][task-1][com.breezek.demo.common.AsyncTask][27]:task1 start
- INFO [17:54:24.566][task-2][com.breezek.demo.common.AsyncTask][36]:task2 start
- INFO [17:54:29.569][task-1][com.breezek.demo.common.AsyncTask][29]:task1 end
- INFO [17:54:34.570][task-2][com.breezek.demo.common.AsyncTask][38]:task2 end
- INFO [17:54:34.570][main][com.breezek.demo.common.AsyncTaskTest][34]:taskTest end
可以看出来, main 线程等待两个子线程执行完毕后再继续向下运行
使用 @Async 注解时, 需要注意以下几点, 否则异步调用不会生效:
异步方法不能定义为 static 类型
调用方法和异步方法不能定义在同一个类中
AsyncConfigurer 配置
下面的代码中是如何配置异步调用使用的线程池, void 返回值异常捕获处理
AsyncConfigurer 接口是 Spring 提供的, 我们定义 JavaConfig 时实现它:
- @Configuration
- @EnableAsync
- @Slf4j
- public class AsyncConfig implements AsyncConfigurer {
- /**
- * 配置线程池, 减少在调用每个异步方法时创建和销毁线程所需的时间
- */
- @Override
- public Executor getAsyncExecutor() {
- // 初始化 Spring 框架提供的线程池
- ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
- // 核心线程数
- executor.setCorePoolSize(10);
- // 最大线程数
- executor.setMaxPoolSize(20);
- // 任务等待队列大小
- executor.setQueueCapacity(10);
- // 任务拒绝策略, 如果线程池拒绝接受任务, 使用调用线程执行
- executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
- // 定义线程名称前缀
- executor.setThreadNamePrefix("async-executor-");
- // 调用线程池初始化方法, 如果在 getAsyncExecutor() 加上了 @Bean 注解, 这个方法可以不调用, 因为 ThreadPoolTaskExecutor 实现了 InitializingBean 接口, Spring 在初始化 Bean 时会调用 InitializingBean.afterPropertiesSet()
- executor.initialize();
- return executor;
- }
- /**
- * void 返回值异步方法异常捕获处理
- */
- @Override
- public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
- return new AsyncExceptionHandler();
- }
- /**
- * 异常捕获处理类
- */
- public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
- @Override
- public void handleUncaughtException(Throwable ex, Method method, Object... params) {
- log.error(String.format("Async method: %s has uncaught exception, params: %s.", method, JSON.toJSONString(params)), ex);
- }
- }
- }
来源: https://www.cnblogs.com/zeekik/p/11735888.html