写在前面的话
哈喽,好久不见,你们还好吗?
今天给大家带来的是我在实际项目上遇到的一个问题。
流程大致是,调用接口,然后将接口返回的数据更新一份到本地数据库,然后返回给前端。更新到本地数据库这个操作原本是用的异步。
国庆回老家,公司打电话来,前端转几秒的圈圈,然后无数据。经查,是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