某些併發環境下Double-check模型的改進

[size=medium]
[b]簡單場景:[/b]
多線程環境,每個線程攜帶惟一的key去組裝數據,相同的key會有相同的數據結果。爲了提高響應速度,在線程訪問的入口處設置緩存。線程根據key先從緩存中取數據,如果緩存中沒有,線程就去做具體的邏輯處理。

模型如下圖:假定每個線程的key如A, B等,同時有多個攜帶同一key的線程進來。[/size]
[img]http://dl.iteye.com/upload/attachment/340914/9e85471b-3baf-3632-88a1-ca1082641855.jpg[/img]

[size=medium]
最基本的處理方式如此:[/size]


private static Map<String, Object> cache
= new ConcurrentHashMap<String, Object>();

//Entry
public Object run(String key) {
Object result = cache.get(key);
if (result == null) {
result = doHardWork(key);
cache.put(key, result);
}

return result;
}

private Object doHardWork(String key) {
Object result = null;
//Concrete work
return result;
}

[size=medium] 它的缺點很明顯,同時會有多個相同key的線程在做事,資源浪費嚴重。

先看段使用[b]Double-check[/b]模式來完成相同功能的代碼:[/size]

private static Map<String, Object> cache
= new ConcurrentHashMap<String, Object>();

public Object run(String key) {
Object result = cache.get(key);//First checking
if (result == null) {
synchronized (cache) {
result = cache.get(key);//Second checking
if (result == null) {
result = doHardWork(key);
cache.put(key, result);
}
}
}

return result;
}

private Object doHardWork(String key) {
Object result = null;

//Concrete work

return result;
}

[size=medium] 假定某個線程T1的參數是A,如果它能從Cache中取到之前A的執行結果,就立馬返回。否則在同步塊外等待,期望此時在同步塊中有另外一個參數也是A的線程T2正在運行,然後將運行結果放入緩存中,在T2執行完成退出同步塊後,T1可以從Cache讀取T2的執行結果,退出請求。Double-check模型有兩次對Cache內容的check,一次在同步塊外,一次在同步塊裏面。它的執行流程如圖:[/size]
[img]http://dl.iteye.com/upload/attachment/340916/57e9ec48-dbb7-3f65-afd9-bee582ea0583.jpg[/img]

[size=medium] 系統初始時,假定有30個參數,每個參數有10個請求線程,那麼同時會有300個線程從Cache中讀數據,在沒有讀到任何數據時,只會有一個線程進入同步塊,其它299個線程在外面等着。[color=blue]Double-check的好處在於,每個參數第一個進入同步塊的線程纔會去執行正式邏輯,其它擁有同樣參數的線程只要從Cache中取數據即可,效率很高。[/color]如果參數A的某個線程之前執行過,其它參數A的線程在進入同步塊後,能從Cache中取到數據,立馬退出同步塊。[color=blue]但同時它的缺點就是因爲有同步塊的存在,每個參數的第一個線程不能並行進入具體邏輯執行過程,得一個一個的來。[/color]如此30個參數,每個參數的第一個線程得依次串行進入具體邏輯。

對於這樣的應用場景,最好的流程是:相同參數的線程只有一個進入具體邏輯,其它線程等待這個參數的執行結果,在得到結果後,直接返回;不同參數的線程在具體邏輯階段可以併發執行。期望的執行流程如下圖:[/size]

[img]http://dl.iteye.com/upload/attachment/340918/6113a393-58c6-3d4b-b410-151d39222e93.jpg[/img]

[size=medium] 這篇帖子的目的是改進Double-check模型的這種缺點,但不是修改Double-check來滿足需求。實現可以很簡單,一是多個線程的數據共享,二是對於同樣參數多個線程的通知。具體模型如下圖:[/size]
[img]http://dl.iteye.com/upload/attachment/340920/f62f1936-3f64-3533-b0c9-f2b7d66d544d.jpg[/img]

[size=large] 從代碼來看:[/size]

/**
* 用來標識當前參數有線程正在做具體邏輯
*/
public static Object lock = new Object();
/**
* 假定參數爲'A',系統初始時檢查lockMap中‘A’的value是否爲null,如果爲null,那當前線程就得做具體邏輯,把'A'的value設置爲固定的lock,其它線程看到有這個lock就什麼事也不做,然後suspend。當有返回數據時,將value由lock替換爲正式返回數據,以在多個線程間共享
*/
private Map<String, Object> lockMap
= new ConcurrentHashMap<String, Object>();

/**
* 所有suspend的線程都要在這裏註冊,以便隨後得到通知
*/
private Map<String, List<Thread>> caller = new ConcurrentHashMap<String, List<Thread>>();



[size=medium] 它的方法有:[/size]

/*
*返回值是lock時,做具體邏輯,返回值不爲lock時,是真正的返回數據,線程得到這個數據,直接返回
*/
public Object runOrWait(String key);

/*
*做具體邏輯的那個線程在做完事後,需要把result寫入共享空間,讓其它線程看到。然後通知所有註冊這個參數的線程知道
*/
public void releaseLock(String key, Object result)


[size=medium] 具體程序見附件,裏面有一個測試類,用來模擬測試Case。然後列舉了以上出現的幾種cache Demo。這個程序只是用來驗證這個處理策略,對於細節問題,值得商榷,歡迎提出意見,十分感謝![/size]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章