java
lock
condition
sync
callback
介紹
大家看完標題是不是覺得就是普通的Future
介紹阀坏,但如果我說這里的異步回調(diào)是指從外部調(diào)用服務(wù)的接口來回調(diào),是不是實現(xiàn)同步就比較麻煩了
先給大家舉個例子笆檀,對于我們現(xiàn)在物聯(lián)網(wǎng)方向的開發(fā)忌堂,或多或少都會接觸到物理設(shè)備的對接,甚至需要對設(shè)備進行控制等操作
但是有很大一部分設(shè)備是不會同步返回結(jié)果的酗洒,而是另外上報一條數(shù)據(jù)士修,這就非常難受了
對于前端頁面的用戶來說枷遂,在下發(fā)了一條命令之后,會更希望能直接得到命令響應(yīng)結(jié)果棋嘲,到底是成功還是失敗
但是由于設(shè)備不會同步返回酒唉,就導致用戶需要切換到其他頁面去看設(shè)備響應(yīng)是否成功
在最開始,因為沒有更好的辦法封字,所以我們預(yù)估了從命令下發(fā)一直到接收到數(shù)據(jù)上報的時間間隔黔州,然后用Thread.sleep
來阻塞線程,不斷查詢是否上報了結(jié)果
可想而知阔籽,這種方式只能說是非常愚蠢流妻,因為非常不好控制,如果等待時間設(shè)置的太短則可能導致大量請求返回不了結(jié)果笆制,如果等待時間設(shè)置的太長則對于用戶的體驗就非常差
于是我就想有沒有一種方式绅这,能夠在命令下發(fā)后就阻塞線程,直到數(shù)據(jù)上報再喚醒在辆,這樣就能非常精確的控制等待時間
我們先來看看怎么使用吧
@RestController
@RequestMapping("/concept-sync-waiting")
public class SyncWaitingController {
/**
* 新建一個 {@link SyncWaitingConcept} 對象
* 也可以直接在 Spring 容器中注入一個全局使用
*/
private final SyncWaitingConcept concept = new ConditionSyncWaitingConcept();
/**
* 下發(fā)命令证薇,阻塞線程直到數(shù)據(jù)上報或超時
*
* @param key 每條命令唯一的id
* @return 設(shè)備上報的數(shù)據(jù)
*/
@RequestMapping("/send")
public String send(@RequestParam String key) {
try {
return concept.waitSync(key, new SyncCaller() {
@Override
public void call(Object k) {
//在這里下發(fā)命令
}
}, 5000);
} catch (SyncWaitingTimeoutException e) {
return "下發(fā)命令超時";
}
}
/**
* 接收設(shè)備上報的數(shù)據(jù),喚醒下發(fā)命令的線程
*
* @param key 一般需要從上報數(shù)據(jù)中附帶命令id
* @param value 上報數(shù)據(jù)
*/
@RequestMapping("/receive")
public void receive(@RequestParam String key, @RequestParam String value) {
concept.notifyAsync(key, value);
}
}
首先創(chuàng)建一個SyncWaitingConcept
對象匆篓,默認實現(xiàn)了ConditionSyncWaitingConcept
SyncWaitingConcept concept = new ConditionSyncWaitingConcept();
然后調(diào)用waitSync
方法浑度,并阻塞當前線程
需要傳入key
作為該次調(diào)用的標識(唯一id),SyncCaller
作為觸發(fā)業(yè)務(wù)邏輯調(diào)用的接口鸦概,waitingTime
作為等待時間限制(小于等于0時則無限等待)
Object value = concept.waitSync(key, new SyncCaller() {
@Override
public void call(Object k) {
//自己的業(yè)務(wù)邏輯箩张,并附帶上key
}
}, 5000);
最后當接收到異步返回的數(shù)據(jù)時,調(diào)用notifyAsync
方法喚醒之前阻塞的線程即可得到接收到的數(shù)據(jù)
需要傳入key
一般在返回數(shù)據(jù)中附帶回來窗市,value
作為接收到的數(shù)據(jù)
concept.notifyAsync(key, value);
是不是還是挺方便的先慷,只要兩個簡單的方法就能阻塞和喚醒線程
如果大家有興趣,Github上的介紹更加詳細咨察,還包括各種高級用法以及整體架構(gòu)
思路
核心思路其實很簡單论熙,就是用Condition
來控制線程的阻塞和喚醒
await
方法可以阻塞當前的線程,進入等待隊列摄狱,signalAll
方法可以喚醒隊列中的所有線程
我把key
和對應(yīng)的Condition
緩存在一個Map
中脓诡,當我們調(diào)用waitSync
方法時
- 先通過
key
查找是否存在等待中的Condition
- 如果已經(jīng)存在炸庞,則調(diào)用
await
讓當前線程排隊阻塞 - 如果不存在醉锄,則調(diào)用回調(diào)接口中的業(yè)務(wù)邏輯
- 然后調(diào)用
await
讓阻塞當前線程 - 接收數(shù)據(jù),根據(jù)
key
獲得對應(yīng)的Condition
- 設(shè)置
key
對應(yīng)的返回值并調(diào)用signalAll
喚醒線程 - 返回
key
得到的值 - 之前直接阻塞的線程也被喚醒并繼續(xù)嘗試執(zhí)行
結(jié)束
基本上的內(nèi)容就是這樣啦