经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring Boot » 查看文章
SpringBoot使用@Async的总结!
来源:cnblogs  作者:雨点的名字  时间:2022/11/21 9:02:55  对本文有异议

一些业务场景我们需要使用多线程异步执行任务,加快任务执行速度。

之前有写过一篇文章叫做: 异步编程利器:CompletableFuture

在实际工作中也更加推荐使用CompletableFuture,因为它实现异步方式更加优雅,而且功能更加强大!

既然SpringBoot能通过 @Async 也实现异步执行任务,那么这篇文章就来总结下如何使用 @Async 实现异步执行任务。

一、SpringBoot使用@Async注解步骤

1、启动类上使用@EnableAsync注解

  1. @SpringBootApplication
  2. @EnableAsync
  3. public class Application {
  4. public static void main(String[] args) {
  5. SpringApplication.run(Application.class, args);
  6. }
  7. }

2、异步方法所在的类注入容器中

  1. @Componet
  2. public class Test{
  3. }

除了@Componet,也可以是@Controller、@RestController、@Service、@Configuration等注解,加入到Ioc容器里。

3、方法上加上@Async注解

  1. @Service
  2. public class Test{
  3. @Async
  4. public void a() {
  5. }
  6. }

二、哪些情况会导致@Async异步失效?

如果你明明是按照上面的步骤来的,但是发现@Async注解还是不起作用,这里还有两点注意,因为@Async是基于Aop思想实现的,所有下面两种情况也会失效。

1、异步方法使用static修饰

  1. @Async
  2. public static void a() {
  3. }

2、调用方法和异步方法在同一个类中

当异步方法和调用方法在同一个类中时,是没办法通过Ioc里的bean来执行异步方法的,从而变成同步方法。

如下:

  1. @Component
  2. public class Task {
  3. /**
  4. * 调异步方法和异步方法在同一个类 @Async执行失败
  5. */
  6. public void dotask() {
  7. this.taskOne();
  8. this.taskTwo();
  9. }
  10. @Async
  11. public void taskOne() {
  12. //执行任务1
  13. }
  14. @Async
  15. public void taskTwo() {
  16. //执行任务2
  17. }
  18. }

三、SpringBoot结合@Async实现异步示例

首先我们来看同步方法

1、同步调用示例

  1. @Component
  2. @Slf4j
  3. public class DemoTask {
  4. public void taskOne() throws Exception {
  5. log.info("===执行任务1===");
  6. long start = System.currentTimeMillis();
  7. Thread.sleep(200);
  8. long end = System.currentTimeMillis();
  9. log.info("任务1执行结束,总耗时={} 毫秒", end - start);
  10. }
  11. public void taskTwo() throws Exception {
  12. log.info("===执行任务2===");
  13. long start = System.currentTimeMillis();
  14. Thread.sleep(200);
  15. long end = System.currentTimeMillis();
  16. log.info("任务2执行结束,总耗时={} 毫秒", end - start);
  17. }
  18. public void taskThere() throws Exception {
  19. log.info("===执行任务3===");
  20. long start = System.currentTimeMillis();
  21. Thread.sleep(200);
  22. long end = System.currentTimeMillis();
  23. log.info("任务3执行结束,总耗时={} 毫秒", end - start);
  24. }
  25. }

执行方法

  1. @Slf4j
  2. @RunWith(SpringRunner.class)
  3. @SpringBootTest
  4. public class DemoTaskTest {
  5. @Autowired
  6. private DemoTask demoTask;
  7. @Test
  8. public void runDemo() throws Exception {
  9. long start = System.currentTimeMillis();
  10. demoTask.taskOne();
  11. demoTask.taskTwo();
  12. demoTask.taskThere();
  13. long end = System.currentTimeMillis();
  14. log.info("总任务执行结束,总耗时={} 毫秒", end - start);
  15. }
  16. }

输出日志

  1. ===执行任务1===
  2. 任务1执行结束,总耗时=204 毫秒
  3. ===执行任务2===
  4. 任务2执行结束,总耗时=203 毫秒
  5. ===执行任务3===
  6. 任务3执行结束,总耗时=201 毫秒
  7. 总任务执行结束,总耗时=613 毫秒

2、异步调用示例

异步方法

  1. @Component
  2. @Slf4j
  3. public class AsyncTask {
  4. @Async
  5. public void taskOne() throws Exception {
  6. //执行内容同上,省略
  7. }
  8. @Async
  9. public void taskTwo() throws Exception {
  10. //执行内容同上,省略
  11. }
  12. @Async
  13. public void taskThere() throws Exception {
  14. //执行内容同上,省略
  15. }
  16. }

调用方法

  1. @Slf4j
  2. @RunWith(SpringJUnit4ClassRunner.class)
  3. @EnableAsync
  4. @SpringBootTest
  5. public class AsyncTest {
  6. @Autowired
  7. private AsyncTask asyncTask;
  8. @Test
  9. public void runAsync() throws Exception {
  10. long start = System.currentTimeMillis();
  11. asyncTask.taskOne();
  12. asyncTask.taskTwo();
  13. asyncTask.taskThere();
  14. Thread.sleep(200);
  15. long end = System.currentTimeMillis();
  16. log.info("总任务执行结束,总耗时={} 毫秒", end - start);
  17. }
  18. }

查看日志

  1. ===执行任务1===
  2. ===执行任务3===
  3. ===执行任务2===
  4. 总任务执行结束,总耗时=206 毫秒
  5. 任务1执行结束,总耗时=200 毫秒
  6. 任务2执行结束,总耗时=201 毫秒
  7. 任务3执行结束,总耗时=201 毫秒

通过日志可以看出已经是已经实现异步处理任务了,而且异步任务哪个先执行是不确定的。

3、Future异步回调

如果我想异步执行,同时想获取所有异步执行的结果,那么这个时候就需要采用Future。

异步方法

  1. @Component
  2. @Slf4j
  3. public class FutureTask {
  4. @Async
  5. public Future<String> taskOne() throws Exception {
  6. //执行内容同上,省略
  7. return new AsyncResult<>("1完成");
  8. }
  9. @Async
  10. public Future<String> taskTwo() throws Exception {
  11. //执行内容同上,省略
  12. return new AsyncResult<>("2完成");
  13. }
  14. @Async
  15. public Future<String> taskThere() throws Exception {
  16. //执行内容同上,省略
  17. return new AsyncResult<>("执行任务3完成");
  18. }
  19. }

调用方法

  1. @Slf4j
  2. @RunWith(SpringRunner.class)
  3. @SpringBootTest
  4. @EnableAsync
  5. public class FutureTaskTest {
  6. @Autowired
  7. private FutureTask futureTask;
  8. @Test
  9. public void runAsync() throws Exception {
  10. long start = System.currentTimeMillis();
  11. Future<String> taskOne = futureTask.taskOne();
  12. Future<String> taskTwo = futureTask.taskTwo();
  13. Future<String> taskThere = futureTask.taskThere();
  14. while (true) {
  15. if (taskOne.isDone() && taskTwo.isDone() && taskThere.isDone()) {
  16. log.info("任务1返回结果={},任务2返回结果={},任务3返回结果={},", taskOne.get(), taskTwo.get(), taskThere.get());
  17. break;
  18. }
  19. }
  20. long end = System.currentTimeMillis();
  21. log.info("总任务执行结束,总耗时={} 毫秒", end - start);
  22. }
  23. }

查看日志

  1. ===执行任务2===
  2. ===执行任务3===
  3. ===执行任务1===
  4. 任务1执行结束,总耗时=201 毫秒
  5. 任务3执行结束,总耗时=201 毫秒
  6. 任务2执行结束,总耗时=201 毫秒
  7. 任务1返回结果=1完成,任务2返回结果=2完成,任务3返回结果=执行任务3完成,
  8. 总任务执行结束,总耗时=223 毫秒

从日志可以看出 异步任务的执行结果都有获取。


四、@Async+自定义线程池实现异步任务

如果不自定义异步方法的线程池默认使用SimpleAsyncTaskExecutor线程池。

SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。并发大的时候会产生严重的性能问题。

Spring也更加推荐我们开发者使用ThreadPoolTaskExecutor类来创建线程池。

自定义线程池

  1. @Configuration
  2. public class ExecutorAsyncConfig {
  3. @Bean(name = "newAsyncExecutor")
  4. public Executor newAsync() {
  5. ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
  6. //设置核心线程数
  7. taskExecutor.setCorePoolSize(10);
  8. // 线程池维护线程的最大数量,只有在缓冲队列满了以后才会申请超过核心线程数的线程
  9. taskExecutor.setMaxPoolSize(100);
  10. //缓存队列
  11. taskExecutor.setQueueCapacity(50);
  12. //允许的空闲时间,当超过了核心线程数之外的线程在空闲时间到达之后会被销毁
  13. taskExecutor.setKeepAliveSeconds(200);
  14. //异步方法内部线程名称
  15. taskExecutor.setThreadNamePrefix("my-xiaoxiao-AsyncExecutor-");
  16. //拒绝策略
  17. taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
  18. taskExecutor.initialize();
  19. return taskExecutor;
  20. }
  21. }

示例代码

任务1和任务2配置走我们自定义的线程池,任务3还是走默认线程池。

  1. @Component
  2. @Slf4j
  3. public class FutureExecutorTask {
  4. @Async("newAsyncExecutor")
  5. public Future<String> taskOne() throws Exception {
  6. log.info("任务1线程名称 = {}", Thread.currentThread().getName());
  7. return new AsyncResult<>("1完成");
  8. }
  9. @Async("newAsyncExecutor")
  10. public Future<String> taskTwo() throws Exception {
  11. log.info("任务2线程名称 = {}", Thread.currentThread().getName());
  12. return new AsyncResult<>("2完成");
  13. }
  14. @Async
  15. public Future<String> taskThere() throws Exception {
  16. log.info("任务3线程名称 = {}", Thread.currentThread().getName());
  17. return new AsyncResult<>("执行任务3完成");
  18. }
  19. }

调研方法和上面一样,我们再来看下日志

  1. 任务2线程名称 = my-xiaoxiao-AsyncExecutor-2
  2. 任务1线程名称 = my-xiaoxiao-AsyncExecutor-1
  3. 任务3线程名称 = SimpleAsyncTaskExecutor-1
  4. 总任务执行结束,总耗时=15 毫秒

通过日志我们可以看出 任务1和任务2走的是我们自定义的线程池,任务3还是走默认线程池。


五、CompletableFuture实现异步任务

推荐这种方式来实现异步,它不需要在启动类上加@EnableAsync注解,也不需要在方法上加@Async注解,它实现更加优雅,而且CompletableFuture功能更加强大。

具体可以看下之前写的文章:异步编程利器:CompletableFuture

1、CompletableFuture示例

看如何使用

  1. @RunWith(SpringJUnit4ClassRunner.class)
  2. @SpringBootTest
  3. public class CompletableTest {
  4. @Autowired
  5. private DemoTask dmoTask;
  6. @Test
  7. public void testCompletableThenRunAsync() throws Exception {
  8. long startTime = System.currentTimeMillis();
  9. CompletableFuture<Void> cp1 = CompletableFuture.runAsync(() -> {
  10. //任务1
  11. dmoTask.taskOne();
  12. });
  13. CompletableFuture<Void> cp2 = CompletableFuture.runAsync(() -> {
  14. //任务2
  15. dmoTask.taskTwo();
  16. });
  17. CompletableFuture<Void> cp3 = CompletableFuture.runAsync(() -> {
  18. //任务3
  19. dmoTask.taskThere();
  20. });
  21. cp1.get();
  22. cp2.get();
  23. cp3.get();
  24. //模拟主程序耗时时间
  25. System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");
  26. }
  27. }

查看日志

  1. ===执行任务1===
  2. ===执行任务2===
  3. ===执行任务3===
  4. 任务3执行结束,总耗时=204 毫秒
  5. 任务2执行结束,总耗时=203 毫秒
  6. 任务1执行结束,总耗时=204 毫秒
  7. 总共用时226ms

从日志可以看出,通过CompletableFuture同样可以实现异步执行任务!



声明: 公众号如需转载该篇文章,发表文章的头部一定要 告知是转至公众号: 后端元宇宙。同时也可以问本人要markdown原稿和原图片。其它情况一律禁止转载!

原文链接:https://www.cnblogs.com/qdhxhz/p/16671089.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号