@Async的使用、应用场景

  • @Async的作用
  • 应用场景
  • @Async的使用
  • 结论
  • 疑问解答

@Async的作用

正常方法被调用时是同步执行,而@Async标识的方法调用时是异步执行。

应用场景

通常用于耗时较长或者不需要立即得到执行结果的逻辑,说白了就是异步执行
例如:

  1. 发送邮件
  2. 导出数据
  3. 同步商品

@Async的使用

这里就同步商品为例,简单说下商品同步的业务便于理解

假设现在有个SaaS化的商城有个商品总库,总库新增了商品,每个企业的商城可以自主同步总库所新增的商品,这个商品同步过程就可以使用@Async异步去实现。

闲话不多说,直接上代码。

项目目录结构:

async function 的类型 async的用法_async function 的类型


要使用@Async首先需要开启该功能,在配置类下加入@EnableAsync

@Configuration
@EnableAsync
public class AppConfig {
}

商品同步接口(按照规范来说Controller中不应该存在业务逻辑,这里为了便于理解,写的可能不是那么规范)

@RestController
@RequiredArgsConstructor
@RequestMapping("/product")
@Slf4j
public class ProductController {

    private final ProductService productService;

    /**
     * 模拟同步商品  测试异步执行(无返回值)
     * @return
     */
    @GetMapping("/syncProduct")
    public ResultVo<String> syncProduct() {
        log.info("syncProduct invoke start");
        productService.syncProduct();
        log.info("syncProduct invoke end");
        return ResultVo.success();
    }

    /**
     * 测试异步执行(有返回值)
     * @return
     */
    @GetMapping("/testAsyncResult")
    public ResultVo<String> testAsyncResult() {
        log.info("syncProduct invoke start");
        Future<String> stringFuture = productService.testAsyncResult();
        try {
            String result = stringFuture.get();
            log.info(result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        log.info("syncProduct invoke end");
        return ResultVo.success();
    }

}

上方包含两个接口

syncProduct:模拟商品同步,使用@Async实现异步执行,没有返回值的情况
testAsyncResult:使用@Async实现异步执行,有返回值的情况

商品Service接口

public interface ProductService {
    void syncProduct();
    Future<String> testAsyncResult();
}

商品Service接口实现

@Service
@Slf4j
public class ProductServiceImpl implements ProductService {

    @Async
    @Override
    public void syncProduct() {
        log.info("同步商品开始");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("同步商品结束");
    }

    @Async
    @Override
    public Future<String> testAsyncResult() {
        log.info("testAsyncResult开始");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("testAsyncResult结束");
        return new AsyncResult<>("执行完成咯!");
    }
}
结论
  1. 调用syncProduct时输出
2021-09-01 19:43:33.123 INFO 28064 — [nio-6659-exec-1] c.f.d.a.controller.ProductController : syncProduct invoke start
 2021-09-01 19:43:33.129 INFO 28064 — [nio-6659-exec-1] c.f.d.a.controller.ProductController : syncProduct invoke end
 2021-09-01 19:43:33.129 INFO 28064 — [ task-1] c.f.d.a.service.impl.ProductServiceImpl : 同步商品开始
 2021-09-01 19:43:38.141 INFO 28064 — [ task-1] c.f.d.a.service.impl.ProductServiceImpl : 同步商品结束1. 可以看到Service中打印日志和Controller中打印日志使用了不一样的线程
2. 观察打印日志时间可以看出syncProduct invoke start 和syncProduct invoke end 打印的间隔不到10毫秒,很明显已经异步执行了。
3. 不需要等 productService.syncProduct 方法执行完,用户已经收到响应接口
{
 “status”: true,
 “code”: “200”,
 “message”: “成功!”,
 “data”: null
 }1. 由此得出 productService.syncProduct 确实异步执行了。
1. 调用testAsyncResult时,有两种情况
1. 如果使用了stringFuture.get()获取执行结果,那么当前线程会阻塞,直到Service执行结束返回结果才会继续向下执行。
 日志打印:2021-09-01 19:56:34.841 INFO 28064 — [nio-6659-exec-4] c.f.d.a.controller.ProductController : syncProduct invoke start
 2021-09-01 19:56:38.011 INFO 28064 — [ task-2] c.f.d.a.service.impl.ProductServiceImpl : testAsyncResult开始
 2021-09-01 19:56:43.022 INFO 28064 — [ task-2] c.f.d.a.service.impl.ProductServiceImpl : testAsyncResult结束
 2021-09-01 19:56:43.023 INFO 28064 — [nio-6659-exec-4] c.f.d.a.controller.ProductController : 执行完成咯!
 2021-09-01 19:56:43.024 INFO 28064 — [nio-6659-exec-4] c.f.d.a.controller.ProductController : syncProduct invoke end1. 如果没有使用stringFuture.get()获取执行结果,和 productService.syncProduct 一样,不会阻塞。
 日志打印:2021-09-01 20:01:21.219 INFO 26060 — [nio-6659-exec-1] c.f.d.a.controller.ProductController : syncProduct invoke start
 2021-09-01 20:01:21.223 INFO 26060 — [nio-6659-exec-1] c.f.d.a.controller.ProductController : syncProduct invoke end
 2021-09-01 20:01:21.223 INFO 26060 — [ task-1] c.f.d.a.service.impl.ProductServiceImpl : testAsyncResult开始
 2021-09-01 20:01:26.224 INFO 26060 — [ task-1] c.f.d.a.service.impl.ProductServiceImpl : testAsyncResult结束

疑问解答

到这里可能有的小伙伴有疑问,如果使用stringFuture.get()获取执行结果,那异步不就没意义了?
确实,在这个简单的例子当中确实没意义。但是有些场景就非常有意义了,例如统计数据,一个接口需要统计优惠券使用情况、商品售卖情况、新增成交用户,如果一个一个轮流执行假设需要20秒,相当于是累加每个统计的执行时长。那如果使用@Async去异步执行统计,然后等待每个统计结果都返回了就统计完了,这样统计执行时长顶多就是统计最久的那个业务逻辑,而不是每个统计累加的执行时长。这种情况就充分体现了使用@Async的意义。