Spring Boot系列之@Async异步调用

简介: Spring Boot系列之@Async异步调用

写在前面的话

哈喽,好久不见,你们还好吗?

今天给大家带来的是我在实际项目上遇到的一个问题。

流程大致是,调用接口,然后将接口返回的数据更新一份到本地数据库,然后返回给前端。更新到本地数据库这个操作原本是用的异步。

国庆回老家,公司打电话来,前端转几秒的圈圈,然后无数据。经查,是Redis出了问题,用不了。

什么意思?

从接口请求到的数据,更新到本地数据库,这里有一个策略,先将数据放到Redis中,然后进行对比,如果不一致,再更新。Redis不可用,那么都查询数据库,就会很慢,前端请求接口一般是5s超时。

如果是异步,也就不会出现这个问题了。

所以,我们就先看看当时,我的代码明明是异步的,为什么没有生效呢?

@Async无效

先看一个例子。

Controller代码如下:

@GetMapping("/invalid")
public String invalidAsyncExample() {
    iTestAsyncService.invalidAsyncExample();
    return "测试完成 " + LocalDateTime.now().toString();
}

Service代码如下:

@Override
public void invalidAsyncExample() {
    log.info("流程-1-{}", Thread.currentThread().getId());
    invalidAsyncTask();
    log.info("流程-3-{}", Thread.currentThread().getId());
}

Async代码如下:

@Async
public void invalidAsyncTask() {
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    log.info("流程-2-{}", Thread.currentThread().getId());
}

执行结果:

2020-11-11 21:14:06.784  INFO 13592 --- [nio-8080-exec-1] c.f.s.a.s.impl.TestAsyncServiceImpl      : 流程-1-125
2020-11-11 21:14:08.785  INFO 13592 --- [nio-8080-exec-1] c.f.s.a.s.impl.TestAsyncServiceImpl      : 流程-2-125
2020-11-11 21:14:08.785  INFO 13592 --- [nio-8080-exec-1] c.f.s.a.s.impl.TestAsyncServiceImpl      : 流程-3-125

结果分析:确实是同步执行,没什么明明加了@Async的,异步没生效呢?

带这个这样一个疑问,在百度上寻找答案。同时,也决定阅读这一块的源码。想看一下,这个异步到底是怎么实现的。

通过阅读源码,会发现,Spring默认是用代理实现异步的。

什么意思?

你可以这样理解,你调用的类需要Spring帮你代理,然后才能异步去执行。

上面的示例代码,invalidAsyncTask(); 调用的方法很明确,不需要代理,这时候Spring也就不能帮你异步去执行了。

关于源码分析,后面在写源码博文的时候,再来。

无返回值的异步任务

首先呢,需要 @EnableAsync

Controller:

@GetMapping("/no-value")
public String noValueAsyncExample() {
    iTestAsyncService.noValueAsyncExample();
    return "测试完成 " + LocalDateTime.now().toString();
}

Service:

@Override
public void noValueAsyncExample() {
    log.info("流程-1-{}", Thread.currentThread().getId());
    iAsyncService.exampleTask();
    log.info("流程-3-{}", Thread.currentThread().getId());
}

耗时任务:

@Override
public void exampleTask() {
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    log.info("耗时任务-2-{}", Thread.currentThread().getId());
}

Async:

@Override
@Async
public void exampleTask() {
    iTestAsyncService.exampleTask();
}

这里要注意,因为我们把耗时的任务放在同一个service里面,所以就会产生循环依赖的问题,需要用到 @Lazy

测试结果:

2020-11-11 22:32:50.019  INFO 18888 --- [nio-8080-exec-7] c.f.s.a.s.impl.TestAsyncServiceImpl      : 流程-1-131
2020-11-11 22:32:50.020  INFO 18888 --- [nio-8080-exec-7] c.f.s.a.s.impl.TestAsyncServiceImpl      : 流程-3-131
2020-11-11 22:32:52.021  INFO 18888 --- [         task-9] c.f.s.a.s.impl.TestAsyncServiceImpl      : 耗时任务-2-152

有返回值的异步任务

也是需要 @EnableAsync

Controller:

@GetMapping("/value")
public int valueAsyncExample() {
    return iTestAsyncService.valueAsyncExample();
}

Service:

@Override
public int valueAsyncExample() {
    int result = 0;

    long startTime = System.currentTimeMillis();

    List<Future<Integer>> futureList = new ArrayList<>();

    for (int i = 0; i < 10; i++) {
        Future<Integer> future = iAsyncService.addTask(i);
        futureList.add(future);
    }

    for (Future<Integer> f : futureList) {
        Integer value = null;
        try {
            value = f.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        if (value != null)
            result += value;
    }

    long endTime = System.currentTimeMillis();

    log.info("耗时 {} s", (endTime - startTime) / 1000D);

    return result;
}

任务:

@Override
public Future<Integer> addTask(int n) {
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    log.info("计算任务-{}", Thread.currentThread().getId());
    return AsyncResult.forValue(n + 2);
}

Async:

@Override
@Async
public Future<Integer> addTask(int i) {
    return iTestAsyncService.addTask(i);
}

同样,这里要注意,因为我们把耗时的任务放在同一个service里面,所以就会产生循环依赖的问题,需要用到 @Lazy

测试结果:

2020-11-11 22:27:05.152  INFO 18888 --- [         task-3] c.f.s.a.s.impl.TestAsyncServiceImpl      : 计算任务-146
2020-11-11 22:27:05.152  INFO 18888 --- [         task-5] c.f.s.a.s.impl.TestAsyncServiceImpl      : 计算任务-148
2020-11-11 22:27:05.152  INFO 18888 --- [         task-4] c.f.s.a.s.impl.TestAsyncServiceImpl      : 计算任务-147
2020-11-11 22:27:05.152  INFO 18888 --- [         task-6] c.f.s.a.s.impl.TestAsyncServiceImpl      : 计算任务-149
2020-11-11 22:27:05.153  INFO 18888 --- [         task-7] c.f.s.a.s.impl.TestAsyncServiceImpl      : 计算任务-150
2020-11-11 22:27:05.152  INFO 18888 --- [         task-2] c.f.s.a.s.impl.TestAsyncServiceImpl      : 计算任务-145
2020-11-11 22:27:05.153  INFO 18888 --- [         task-8] c.f.s.a.s.impl.TestAsyncServiceImpl      : 计算任务-151
2020-11-11 22:27:05.152  INFO 18888 --- [         task-1] c.f.s.a.s.impl.TestAsyncServiceImpl      : 计算任务-144
2020-11-11 22:27:07.154  INFO 18888 --- [         task-6] c.f.s.a.s.impl.TestAsyncServiceImpl      : 计算任务-149
2020-11-11 22:27:07.154  INFO 18888 --- [         task-3] c.f.s.a.s.impl.TestAsyncServiceImpl      : 计算任务-146
2020-11-11 22:27:07.154  INFO 18888 --- [nio-8080-exec-1] c.f.s.a.s.impl.TestAsyncServiceImpl      : 耗时 4.006 s

页面结果

65

测试代码

https://github.com/fengwenyi/study-spring-boot/tree/master/spring-boot-async

目录
相关文章
|
5月前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
395 0
|
6天前
|
Java 测试技术 Spring
SpringBoot+@Async注解一起用,速度提升
本文介绍了异步调用在高并发Web应用性能优化中的重要性,对比了同步与异步调用的区别。同步调用按顺序执行,每一步需等待上一步完成;而异步调用无需等待,可提升效率。通过Spring Boot示例,使用@Async注解实现异步任务,并借助Future对象处理异步回调,有效减少程序运行时间。
|
3天前
|
Java 数据库 微服务
微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——指定项目配置文件
在实际项目中,开发环境和生产环境的配置往往不同。为简化配置切换,可通过创建 `application-dev.yml` 和 `application-pro.yml` 分别管理开发与生产环境配置,如设置不同端口(8001/8002)。在 `application.yml` 中使用 `spring.profiles.active` 指定加载的配置文件,实现环境快速切换。本节还介绍了通过配置类读取参数的方法,适用于微服务场景,提升代码可维护性。课程源码可从 [Gitee](https://gitee.com/eson15/springboot_study) 下载。
19 0
|
2月前
|
XML Java 应用服务中间件
Spring Boot 两种部署到服务器的方式
本文介绍了Spring Boot项目的两种部署方式:jar包和war包。Jar包方式使用内置Tomcat,只需配置JDK 1.8及以上环境,通过`nohup java -jar`命令后台运行,并开放服务器端口即可访问。War包则需将项目打包后放入外部Tomcat的webapps目录,修改启动类继承`SpringBootServletInitializer`并调整pom.xml中的打包类型为war,最后启动Tomcat访问应用。两者各有优劣,jar包更简单便捷,而war包适合传统部署场景。需要注意的是,war包部署时,内置Tomcat的端口配置不会生效。
654 17
Spring Boot 两种部署到服务器的方式
|
7月前
|
Java 开发者 Spring
【SpringBoot 异步魔法】@Async 注解:揭秘 SpringBoot 中异步方法的终极奥秘!
【8月更文挑战第25天】异步编程对于提升软件应用的性能至关重要,尤其是在高并发环境下。Spring Boot 通过 `@Async` 注解简化了异步方法的实现。本文详细介绍了 `@Async` 的基本用法及配置步骤,并提供了示例代码展示如何在 Spring Boot 项目中创建与管理异步任务,包括自定义线程池、使用 `CompletableFuture` 处理结果及异常情况,帮助开发者更好地理解和运用这一关键特性。
504 1
|
4月前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
127 2
|
5月前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
160 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
5月前
|
缓存 NoSQL Java
Springboot自定义注解+aop实现redis自动清除缓存功能
通过上述步骤,我们不仅实现了一个高度灵活的缓存管理机制,还保证了代码的整洁与可维护性。自定义注解与AOP的结合,让缓存清除逻辑与业务逻辑分离,便于未来的扩展和修改。这种设计模式非常适合需要频繁更新缓存的应用场景,大大提高了开发效率和系统的响应速度。
143 2
|
6月前
|
Java Spring 容器
Spring使用异步注解@Async正确姿势
Spring使用异步注解@Async正确姿势,异步任务,spring boot
|
7月前
|
Java Scala Kotlin
SpringBoot中@Async的实现方式探索
本文探讨了一段重写了默认`@Async`线程处理的Scala代码,并对其目的与必要性提出了疑问。原代码通过自定义`AsyncExecutorConfig`类,实现了`AsyncConfigurerSupport`接口,进而配置了一个`ThreadPoolExecutor`作为异步任务的执行器。文章随后分析了为何要替换默认实现,并对Spring框架中`@Async`注解的默认行为进行了深入研究。

热门文章

最新文章