美文网首页
Spring Boot系列之@Async异步调用

Spring Boot系列之@Async异步调用

作者: 冯文议 | 来源:发表于2020-11-11 23:04 被阅读0次

    写在前面的话

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

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

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

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

    什么意思?

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

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

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

    @Async无效

    先看一个例子。

    Controller代码如下:

    <pre mdtype="fences" cid="n31" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@GetMapping("/invalid")
    public String invalidAsyncExample() {
    iTestAsyncService.invalidAsyncExample();
    return "测试完成 " + LocalDateTime.now().toString();
    }</pre>

    Service代码如下:

    <pre mdtype="fences" cid="n35" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Override
    public void invalidAsyncExample() {
    log.info("流程-1-{}", Thread.currentThread().getId());
    invalidAsyncTask();
    log.info("流程-3-{}", Thread.currentThread().getId());
    }</pre>

    Async代码如下:

    <pre mdtype="fences" cid="n43" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Async
    public void invalidAsyncTask() {
    try {
    Thread.sleep(2000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    log.info("流程-2-{}", Thread.currentThread().getId());
    }</pre>

    执行结果:

    <pre mdtype="fences" cid="n49" lang="" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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</pre>

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

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

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

    什么意思?

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

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

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

    无返回值的异步任务

    首先呢,需要 @EnableAsync

    Controller:

    <pre mdtype="fences" cid="n105" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@GetMapping("/no-value")
    public String noValueAsyncExample() {
    iTestAsyncService.noValueAsyncExample();
    return "测试完成 " + LocalDateTime.now().toString();
    }</pre>

    Service:

    <pre mdtype="fences" cid="n111" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Override
    public void noValueAsyncExample() {
    log.info("流程-1-{}", Thread.currentThread().getId());
    iAsyncService.exampleTask();
    log.info("流程-3-{}", Thread.currentThread().getId());
    }</pre>

    耗时任务:

    <pre mdtype="fences" cid="n117" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Override
    public void exampleTask() {
    try {
    Thread.sleep(2000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    log.info("耗时任务-2-{}", Thread.currentThread().getId());
    }</pre>

    Async:

    <pre mdtype="fences" cid="n123" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Override
    @Async
    public void exampleTask() {
    iTestAsyncService.exampleTask();
    }</pre>

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

    测试结果:

    <pre mdtype="fences" cid="n134" lang="" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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</pre>

    有返回值的异步任务

    也是需要 @EnableAsync

    Controller:

    <pre mdtype="fences" cid="n148" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@GetMapping("/value")
    public int valueAsyncExample() {
    return iTestAsyncService.valueAsyncExample();
    }</pre>

    Service:

    <pre mdtype="fences" cid="n156" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@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;
    }</pre>

    任务:

    <pre mdtype="fences" cid="n162" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@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);
    }</pre>

    Async:

    <pre mdtype="fences" cid="n168" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Override
    @Async
    public Future<Integer> addTask(int i) {
    return iTestAsyncService.addTask(i);
    }</pre>

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

    测试结果:

    <pre mdtype="fences" cid="n178" lang="" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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</pre>

    页面结果

    <pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n185" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 0px; margin-top: 0px; width: inherit;">65</pre>

    测试代码

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

    相关文章

      网友评论

          本文标题:Spring Boot系列之@Async异步调用

          本文链接:https://www.haomeiwen.com/subject/cftubktx.html