Guarded Suspension 模式
項目組團建要外出聚餐,我們提前預訂了一個包廂,然後興沖沖地奔過去,到那兒後大堂經理看了一眼包廂,發現服務員正在收拾,就會告訴我們:“您預訂的包廂服務員正在收拾,請您稍等片刻。”過了一會,大堂經理發現包廂已經收拾完了,于是馬上帶我們去包廂就餐。
我們等待包廂收拾完的這個過程和小灰遇到的等待 MQ 傳回消息本質上是一樣的,都是等待一個條件滿足:就餐需要等待包廂收拾完,小灰的程式裡要等待 MQ 傳回消息。
那我們來看看現實世界裡是如何解決這類問題的呢?現實世界裡大堂經理這個角色很重要,我們是否等待,完全是由他來協調的。通過類比,相信你也一定有思路了:我們的程式裡,也需要這樣一個大堂經理。的确是這樣,那程式世界裡的大堂經理該如何設計呢?其實設計方案前人早就搞定了,而且還将其總結成了一個設計模式:Guarded Suspension。所謂 Guarded Suspension,直譯過來就是“保護性地暫停”。那下面我們就來看看,Guarded Suspension 模式是如何模拟大堂經理進行保護性地暫停的。
下圖就是 Guarded Suspension 模式的結構圖,非常簡單,一個對象 GuardedObject,内部有一個成員變量——受保護的對象,以及兩個成員方法——get(Predicate p)和onChanged(T obj)方法。其中,對象 GuardedObject 就是我們前面提到的大堂經理,受保護對象就是餐廳裡面的包廂;受保護對象的 get() 方法對應的是我們的就餐,就餐的前提條件是包廂已經收拾好了,參數 p 就是用來描述這個前提條件的;受保護對象的 onChanged() 方法對應的是服務員把包廂收拾好了,通過 onChanged() 方法可以 fire 一個事件,而這個事件往往能改變前提條件 p 的計算結果。下圖中,左側的綠色線程就是需要就餐的顧客,而右側的藍色線程就是收拾包廂的服務員。
Guarded Suspension 模式結構圖
GuardedObject 的内部實作非常簡單,是管程的一個經典用法,你可以參考下面的示例代碼,核心是:get() 方法通過條件變量的 await() 方法實作等待,onChanged() 方法通過條件變量的 signalAll() 方法實作喚醒功能。邏輯還是很簡單的,是以這裡就不再詳細介紹了。
class GuardedObject{
//受保護的對象
T obj;
final Lock lock =
new ReentrantLock();
final Condition done =
lock.newCondition();
final int timeout=1;
//擷取受保護對象
T get(Predicate p) {
lock.lock();
try {
//MESA管程推薦寫法
while(!p.test(obj)){
done.await(timeout,
TimeUnit.SECONDS);
}
}catch(InterruptedException e){
throw new RuntimeException(e);
}finally{
lock.unlock();
}
//傳回非空的受保護對象
return obj;
}
//事件通知方法
void onChanged(T obj) {
lock.lock();
try {
this.obj = obj;
done.signalAll();
} finally {
lock.unlock();
}
}
}