本文已参与「新人创作礼」活动,一起开启掘金创作之路。
@Async 异步注解
SpringBoot 中提供了异步线程的注解,@Async
标注在方法上即可开启异步线程。调用者将在调用时立即返回,方法的实际执行将提交给 Spring TaskExecutor 的任务中,由指定的线程池中的线程执行。
实例-异步线程
(1) 使用步骤很简单:
- 在启动类上添加
@EnableAsync
注解,开启异步注解; - 编写自定义的异步线程配置
如果没有这步的话,异步线程实际上使用的是默认线程池,而默认线程池并不推荐使用.
而 @Async 注解的 value 属性可用于指定执行此异步方法的线程池
.
(2) 代码:
代码在 Github 上地址如下:
(3) 测试:
① 使用自定义线程池时的表现和输出:
表现:接口调用迅速返回结果.
日志输出如下:可以看到使用了自定义的线程池.
2022-05-23 21:15:16.422 INFO 53604 --- [nio-8080-exec-5] it.com.controller.UserController : i:0,时间:Mon May 23 21:15:16 CST 2022
2022-05-23 21:15:16.422 INFO 53604 --- [nio-8080-exec-5] it.com.controller.UserController : i:1,时间:Mon May 23 21:15:16 CST 2022
2022-05-23 21:15:16.422 INFO 53604 --- [nio-8080-exec-5] it.com.controller.UserController : i:2,时间:Mon May 23 21:15:16 CST 2022
2022-05-23 21:15:16.423 INFO 53604 --- [nio-8080-exec-5] it.com.controller.UserController : i:3,时间:Mon May 23 21:15:16 CST 2022
2022-05-23 21:15:16.423 INFO 53604 --- [nio-8080-exec-5] it.com.controller.UserController : i:4,时间:Mon May 23 21:15:16 CST 2022
2022-05-23 21:15:17.424 INFO 53604 --- [Async-Service-6] it.com.async.UserAsyncService : 线程: Async-Service-6, 时间:Mon May 23 21:15:17 CST 2022
2022-05-23 21:15:17.424 INFO 53604 --- [sync-Service-10] it.com.async.UserAsyncService : 线程: Async-Service-10, 时间:Mon May 23 21:15:17 CST 2022
2022-05-23 21:15:17.424 INFO 53604 --- [Async-Service-8] it.com.async.UserAsyncService : 线程: Async-Service-8, 时间:Mon May 23 21:15:17 CST 2022
2022-05-23 21:15:17.424 INFO 53604 --- [Async-Service-9] it.com.async.UserAsyncService : 线程: Async-Service-9, 时间:Mon May 23 21:15:17 CST 2022
2022-05-23 21:15:17.424 INFO 53604 --- [Async-Service-7] it.com.async.UserAsyncService : 线程: Async-Service-7, 时间:Mon May 23 21:15:17 CST 2022
复制代码
② 不使用自定义线程池时 (即去掉 自定义线程的代码) 的表现和输出:
表现:接口调用迅速返回结果.
日志输出如下:可以看到使用了默认的线程池.
2022-05-23 21:24:03.085 INFO 9424 --- [nio-8080-exec-6] it.com.controller.UserController : i:0,时间:Mon May 23 21:24:03 CST 2022
2022-05-23 21:24:03.086 INFO 9424 --- [nio-8080-exec-6] it.com.controller.UserController : i:1,时间:Mon May 23 21:24:03 CST 2022
2022-05-23 21:24:03.086 INFO 9424 --- [nio-8080-exec-6] it.com.controller.UserController : i:2,时间:Mon May 23 21:24:03 CST 2022
2022-05-23 21:24:03.086 INFO 9424 --- [nio-8080-exec-6] it.com.controller.UserController : i:3,时间:Mon May 23 21:24:03 CST 2022
2022-05-23 21:24:03.086 INFO 9424 --- [nio-8080-exec-6] it.com.controller.UserController : i:4,时间:Mon May 23 21:24:03 CST 2022
2022-05-23 21:24:04.102 INFO 9424 --- [ task-8] it.com.async.UserAsyncService : 线程: task-8, 时间:Mon May 23 21:24:04 CST 2022
2022-05-23 21:24:04.102 INFO 9424 --- [ task-4] it.com.async.UserAsyncService : 线程: task-4, 时间:Mon May 23 21:24:04 CST 2022
2022-05-23 21:24:04.102 INFO 9424 --- [ task-7] it.com.async.UserAsyncService : 线程: task-7, 时间:Mon May 23 21:24:04 CST 2022
2022-05-23 21:24:04.102 INFO 9424 --- [ task-5] it.com.async.UserAsyncService : 线程: task-5, 时间:Mon May 23 21:24:04 CST 2022
2022-05-23 21:24:04.102 INFO 9424 --- [ task-1] it.com.async.UserAsyncService : 线程: task-1, 时间:Mon May 23 21:24:04 CST 2022
复制代码
③ 不使用异步注解 (即去掉 @Async 注解) 的表现和输出:
表现:接口调用并不会立刻返回结果,而是在等待 5s 后返回.
日志输出如下:可以看到没有使用任何线程,就是同步执行下来的.
2022-05-23 21:27:37.608 INFO 568 --- [nio-8080-exec-2] it.com.controller.UserController : i:0,时间:Mon May 23 21:27:37 CST 2022
2022-05-23 21:27:38.615 INFO 568 --- [nio-8080-exec-2] it.com.async.UserAsyncService : 线程: http-nio-8080-exec-2, 时间:Mon May 23 21:27:38 CST 2022
2022-05-23 21:27:38.615 INFO 568 --- [nio-8080-exec-2] it.com.controller.UserController : i:1,时间:Mon May 23 21:27:38 CST 2022
2022-05-23 21:27:39.629 INFO 568 --- [nio-8080-exec-2] it.com.async.UserAsyncService : 线程: http-nio-8080-exec-2, 时间:Mon May 23 21:27:39 CST 2022
2022-05-23 21:27:39.629 INFO 568 --- [nio-8080-exec-2] it.com.controller.UserController : i:2,时间:Mon May 23 21:27:39 CST 2022
2022-05-23 21:27:40.630 INFO 568 --- [nio-8080-exec-2] it.com.async.UserAsyncService : 线程: http-nio-8080-exec-2, 时间:Mon May 23 21:27:40 CST 2022
2022-05-23 21:27:40.630 INFO 568 --- [nio-8080-exec-2] it.com.controller.UserController : i:3,时间:Mon May 23 21:27:40 CST 2022
2022-05-23 21:27:41.633 INFO 568 --- [nio-8080-exec-2] it.com.async.UserAsyncService : 线程: http-nio-8080-exec-2, 时间:Mon May 23 21:27:41 CST 2022
2022-05-23 21:27:41.633 INFO 568 --- [nio-8080-exec-2] it.com.controller.UserController : i:4,时间:Mon May 23 21:27:41 CST 2022
2022-05-23 21:27:42.648 INFO 568 --- [nio-8080-exec-2] it.com.async.UserAsyncService : 线程: http-nio-8080-exec-2, 时间:Mon May 23 21:27:42 CST 2022
复制代码
\
异步线程需要注意的地方
必须满足如下这些条件,@Async 注解才不会失效:
- 异步注解 @Async 只能使用在
public void
方法上. 不能使用 static 修饰. - 异步注解标注的方法和调用方
不能在同一个类
中,否则无法生效. - 异步类需要使用
@Component
注解被 Spring 管理. 因为 @Async 也是 AOP 机制. - 如果使用 SpringBoot 必须在启动类中增加
@EnableAsync
注解.
\
@Async 如何使用线程池
Spring 在执行 @Async 标识的异步方法的时候,首先会在 Spring 的上下文中搜索 类型为 TaskExecutor
或者名称为 taskExecutor
的 bean,当可以找到的时候,就将任务提交到此线程池中执行.
当不存在以上线程池的时候,Spring 会自动创建一个 SimpleAsyncTaskExecutor 来执行异步任务.
如果 @Async 注解指定了 value 值,Spring 则会使用指定的线程池来执行。比如:
@Async(value = "myTaskExecutor")
复制代码
这个时候 Spring 会去上下文中找名字为 myTaskExecutor 的 bean,并执行异步任务,找不到,会抛出异常
.
如果你自定义的线程池也正好叫 taskExecutor,那么此处就相当于走逻辑一.
异步线程获取执行结果
- 异步任务不需要返回值的,任务方法返回 void 即可.
@Async("myTaskExecutor") // 使用指定线程池
public void getUserInfo() {
String name = Thread.currentThread().getName();
try {
TimeUnit.SECONDS.sleep(5L);
} catch (InterruptedException e) {
log.info("线程: {}, 时间:{},异常:{}", name, new Date().toString(), e.getMessage());
}
log.info("线程: {}, 时间:{}", name, new Date().toString());
}
复制代码
- 异步任务需要返回值的,任务方法返回
AsyncResult
即可.
@Async("myTaskExecutor") // 使用指定线程池
public Future<User> getUserInfo2() {
String name = Thread.currentThread().getName();
try {
TimeUnit.SECONDS.sleep(50L);
} catch (InterruptedException e) {
log.info("线程: {}, 时间:{},异常:{}", name, new Date().toString(), e.getMessage());
}
log.info("线程: {}, 时间:{}", name, new Date().toString());
User user = new User();
user.setId(id).setName(name);
return new AsyncResult<>(user);
}
复制代码
- 异步任务需要多个返回值组装的,任务方法返回
CompletableFuture
即可.
@Async("myTaskExecutor") // 使用指定线程池
public CompletableFuture<User> getUserInfo3() {
int id = (int) Thread.currentThread().getId();
String name = Thread.currentThread().getName();
try {
TimeUnit.SECONDS.sleep(2L);
} catch (InterruptedException e) {
log.info("线程: {}, 时间:{},异常:{}", name, new Date().toString(), e.getMessage());
}
User user = new User();
user.setId(id).setName(name);
log.info("getUserInfo3执行完毕, 线程: {}, 时间:{}", name, new Date().toString());
return CompletableFuture.completedFuture(user);
}
复制代码
\