java
lock
condition
sync
callback
大家看完标题是不是觉得就是普通的Future
介绍,但如果我说这里的异步回调是指从外部调用服务的接口来回调,是不是实现同步就比较麻烦了
先给大家举个例子,对于我们现在物联网方向的开发,或多或少都会接触到物理设备的对接,甚至需要对设备进行控制等操作
但是有很大一部分设备是不会同步返回结果的,而是另外上报一条数据,这就非常难受了
对于前端页面的用户来说,在下发了一条命令之后,会更希望能直接得到命令响应结果,到底是成功还是失败
但是由于设备不会同步返回,就导致用户需要切换到其他页面去看设备响应是否成功
在最开始,因为没有更好的办法,所以我们预估了从命令下发一直到接收到数据上报的时间间隔,然后用Thread.sleep
来阻塞线程,不断查询是否上报了结果
可想而知,这种方式只能说是非常愚蠢,因为非常不好控制,如果等待时间设置的太短则可能导致大量请求返回不了结果,如果等待时间设置的太长则对于用户的体验就非常差
于是我就想有没有一种方式,能够在命令下发后就阻塞线程,直到数据上报再唤醒,这样就能非常精确的控制等待时间
所以我就写了一个工具来实现这样的功能
我们先来看看怎么使用吧
@RestController
@RequestMapping("/concept-sync-waiting")
public class SyncWaitingController {
/**
* 新建一个 {@link SyncWaitingConcept} 对象
* 也可以直接在 Spring 容器中注入一个全局使用
*/
private final SyncWaitingConcept concept = new ConditionSyncWaitingConcept();
/**
* 下发命令,阻塞线程直到数据上报或超时
*
* @param key 每条命令唯一的id
* @return 设备上报的数据
*/
@RequestMapping("/send")
public String send(@RequestParam String key) {
try {
return concept.waitSync(key, new SyncCaller() {
@Override
public void call(Object k) {
//在这里下发命令
}
}, 5000);
} catch (SyncWaitingTimeoutException e) {
return "下发命令超时";
}
}
/**
* 接收设备上报的数据,唤醒下发命令的线程
*
* @param key 一般需要从上报数据中附带命令id
* @param value 上报数据
*/
@RequestMapping("/receive")
public void receive(@RequestParam String key, @RequestParam String value) {
concept.notifyAsync(key, value);
}
}
首先创建一个SyncWaitingConcept
对象,默认实现了ConditionSyncWaitingConcept
SyncWaitingConcept concept = new ConditionSyncWaitingConcept();
然后调用waitSync
方法,并阻塞当前线程
需要传入key
作为该次调用的标识(唯一id),SyncCaller
作为触发业务逻辑调用的接口,waitingTime
作为等待时间限制(小于等于0时则无限等待)
Object value = concept.waitSync(key, new SyncCaller() {
@Override
public void call(Object k) {
//自己的业务逻辑,并附带上key
}
}, 5000);
最后当接收到异步返回的数据时,调用notifyAsync
方法唤醒之前阻塞的线程即可得到接收到的数据
需要传入key
一般在返回数据中附带回来,value
作为接收到的数据
concept.notifyAsync(key, value);
是不是还是挺方便的,只要两个简单的方法就能阻塞和唤醒线程
如果大家有兴趣,Github上的介绍更加详细,还包括各种高级用法以及整体架构
核心思路其实很简单,就是用Condition
来控制线程的阻塞和唤醒
await
方法可以阻塞当前的线程,进入等待队列,signalAll
方法可以唤醒队列中的所有线程
我把key
和对应的Condition
缓存在一个Map
中,当我们调用waitSync
方法时
key
查找是否存在等待中的Condition
await
让当前线程排队阻塞await
让阻塞当前线程key
获得对应的Condition
key
对应的返回值并调用signalAll
唤醒线程key
得到的值【Spring Cloud】协同开发利器之动态路由
【Spring Cloud】一个配置注解实现 WebSocket 集群方案
【Java】简单优雅的加载外部 jar 中的 Class|插件化
【Spring Boot】一个注解实现下载接口
【Spring Boot】WebSocket 的 6 种集成方式