序言
本节将学习一下如何实现异步查询转同步的方式,共计介绍了 7 种常见的实现方式。
思维导图如下:
![37d5c519f100bfb87a84f57be97b20ef.png](https://i-blog.csdnimg.cn/blog_migrate/d96151938cf8097089129a51fd294865.png)
异步转同步
业务需求
有些接口查询反馈结果是异步返回的,无法立刻获取查询结果。
比如业务开发中我们调用其他系统,但是结果的返回确实通知的。
或者 rpc 实现中,client 调用 server 端,结果也是异步返回的,那么如何同步获取调用结果呢?
- 正常处理逻辑
触发异步操作,然后传递一个唯一标识。
等到异步结果返回,根据传入的唯一标识,匹配此次结果。
- 如何转换为同步
正常的应用场景很多,但是有时候不想做数据存储,只是想简单获取调用结果。
即想达到同步操作的结果,怎么办呢?
思路
发起异步操作
在异步结果返回之前,一直等待(可以设置超时)
结果返回之后,异步操作结果统一返回
常见的实现方式
循环等待
wait & notify
使用条件锁
使用 CountDownLatch
使用 CyclicBarrier
Future
Spring EventListener
下面我们一起来学习下这几种实现方式。
循环等待
说明
循环等待是最简单的一种实现思路。
我们调用对方一个请求,在没有结果之前一直循环查询即可。
这个结果可以在内存中,也可以放在 redis 缓存或者 mysql 等数据库中。
代码实现
定义抽象父类
为了便于后面的其他几种实现方式统一,我们首先定义一个抽象父类。
/**
* 抽象查询父类
* @author binbin.hou
* @since 1.0.0
*/
public abstract class AbstractQuery {
private static final Log log = LogFactory.getLog(AbstractQuery.class);
protected String result;
public void asyncToSync() {
startQuery();
new Thread(new Runnable() {
public void run() {
remoteCall();
}
}).start();
endQuery();
}
protected void startQuery() {
log.info("开始查询...");
}
/**
* 远程调用
*/
protected void remoteCall() {
try {
log.info("远程调用开始");
TimeUnit.SECONDS.sleep(5);
result = "success";
log.info("远程调用结束");
} catch (InterruptedException e) {
log.error("远程调用失败", e);
}
}
/**
* 查询结束
*/
protected void endQuery() {
log.info("完成查询,结果为:" + result);
}
}
代码实现
实现还是非常简单的,在没有结果之前一直循环。
TimeUnit.MILLISECONDS.sleep(10);
这里循环等待的小睡一会儿是比较重要的,避免 cpu 飙升,也可以降低为 1ms,根据自己的业务调整即可。
/**
* 循环等待
* @author binbin.hou
* @since 1.0.0
*/
public class LoopQuery extends AbstractQuery {
private static final Log log = LogFactory.getLog(LoopQuery.class);
@Override
protected void endQuery() {
try {
while (StringUtil.isEmpty(result)) {
//循环等待一下
TimeUnit.MILLISECONDS.sleep(10);
}
//获取结果
log.info("完成查询,结果为:" + result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试
LoopQuery loopQuery = new LoopQuery();
loopQuery.asyncToSync();
- 日志
[INFO] [2020-10-08 09:50:43.330] [main] [c.g.h.s.t.d.AbstractQuery.startQuery] - 开始查询...
[INFO] [2020-10-08 09:50:43.331] [Thread-0] [c.g.h.s.t.d.AbstractQuery.remoteCall] - 远程调用开始
[INFO] [2020-10-08 09:50:48.334] [Thread-0] [c.g.h.s.t.d.AbstractQuery.remoteCall] - 远程调用结束
[INFO] [2020-10-08 09:50:48.343] [main] [c.g.h.s.t.d.LoopQuery.endQuery] - 完成查询,结果为:success
这里可以看到远程调用是 Thread-0
线程执行的,远程调用的耗时为 5S。
超时特性
为什么需要超时时间
上面的实现存在一个问题,那就是循环等待没有超时时间。
我们的一个网络请求,可能存在失败,也可能对方收到请求之后没有正确处理。
所以如果我们一直等待,可能永远也没有结果,或者很久之后才有结果。这在业务上是不可忍受的,所以需要添加一个超时时间。
代码实现
/**
* 循环等待-包含超时时间
* @author binbin.hou
* @since 1.0.0
*/
public class LoopTimeoutQuery extends AbstractQuery {
private static final Log log = LogFactory.getLog(LoopTimeoutQuery.class);
/**
* 超时时间
*/
private long timeoutMills = 3000;
public Loop