@Async的使用、应用场景
- @Async的作用
- 应用场景
- @Async的使用
- 结论
- 疑问解答
@Async的作用
正常方法被调用时是同步执行,而@Async标识的方法调用时是异步执行。
应用场景
通常用于耗时较长或者不需要立即得到执行结果的逻辑,说白了就是异步执行
例如:
- 发送邮件
- 导出数据
- 同步商品
@Async的使用
这里就同步商品为例,简单说下商品同步的业务便于理解
假设现在有个SaaS化的商城有个商品总库,总库新增了商品,每个企业的商城可以自主同步总库所新增的商品,这个商品同步过程就可以使用@Async异步去实现。
闲话不多说,直接上代码。
项目目录结构:
要使用@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<>("执行完成咯!");
}
}
结论
- 调用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的意义。