SpringBoot-@Async 异步线程注解

·  阅读 618

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

@Async 异步注解

SpringBoot 中提供了异步线程的注解,@Async标注在方法上即可开启异步线程。调用者将在调用时立即返回,方法的实际执行将提交给 Spring TaskExecutor 的任务中,由指定的线程池中的线程执行。

实例-异步线程

(1) 使用步骤很简单:

  • 在启动类上添加@EnableAsync注解,开启异步注解;
  • 编写自定义的异步线程配置

如果没有这步的话,异步线程实际上使用的是默认线程池,而默认线程池并不推荐使用.

而 @Async 注解的 value 属性可用于指定执行此异步方法的线程池.

(2) 代码:

代码在 Github 上地址如下:

github.com/SanJingYe88…

(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);
}
复制代码

\

分类:
后端
收藏成功!
已添加到「」, 点击更改