瓜娃之走馬觀花 (2) - Make me a Map as fast as you can

古人云 (無圖無真相, 有美女走光圖爲證):
[quote]Pat-a-map, Pat-a-map, maker's man,
Make me a map as fast as you can...[/quote]

[img]http://upload.wikimedia.org/wikipedia/commons/thumb/0/09/Pat-a-cake%2C_pat-a-cake%2C_baker%27s_man_1_-_WW_Denslow_-_Project_Gutenberg_etext_18546.jpg/200px-Pat-a-cake%2C_pat-a-cake%2C_baker%27s_man_1_-_WW_Denslow_-_Project_Gutenberg_etext_18546.jpg[/img]

這集要講的工具叫[url=http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/common/collect/MapMaker.html]MapMaker[/url].

顧名思義, MapMaker就是幫你做Map滴, 而且是as fast as it can! 實在是居家旅行, 製作Map的必備工具啊!

可是啊, 這計算機一思考, 你就笑了: "小樣兒, 做Map還用你做啊? 當我文盲?" 不就是:
new HashMap<姓名, 獎金>()
嘛. 如果用上回書提到的Maps就是:
Maps.newHashMap();

再不, 用ImmutableMap, 就是
ImmutableMap.of("ajoo", 10000000000000000);


計算機: 這個, 這個, 啊, (耳語: 老大, 別這麼不給面子啊. 出來混都不容易), 我們這裏講的不是HashMap, 也不是ImmutableMap, 其實是代表着當代最最高科技的
java.util.concurrent.ConcurrentMap
, 它適合多線程操作, 高併發, 無阻塞, 無死鎖, 無衝突, 無測漏, 無...

你: 等等, 你是在說:
new ConcurrentHashMap<姓名, 獎金>()
這麼簡單到離譜的東西麼?

計算機: 這個, 別急, 別急. 其實不完全是. MapMaker提供了更多更強大的功能. 比如可以指定鍵是否用弱引用, 指定一個鍵多長時間過期之類的. 但是最最最有用的, 是那個[url=http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/common/collect/MapMaker.html#makeComputingMap(com.google.common.base.Function)]makeComputingMap()[/url]函數.

畫面一轉, ajoo平易近人地微笑着說: 對啦! 這也就是我今天要講MapMaker的主要原因.

大家想一想, 平時有沒有需要做延遲初始化(所謂lazy initialization)和緩存的? 比如說, 我要對每一個需要用到的員工名字通過數據庫查詢她一年以來的加班多少, 加班是否自覺自願, 漂亮程度, 身材和三圍指標, 領會領導意圖能力, 幫領導擋酒總加侖數等等等等各種數據, 最後經過一個NP問題的近似解決算法來得到員工的獎金數額.

這個東西算起來太費資源, 弄不好比獎金本身還多. 那麼, 就要延遲計算, 只有員工主動跑來跟我要加薪, 我纔去算. 而且要緩存, 如果這個員工對只發50元獎金心存不滿, 反覆來吵鬧, 甚至投訴, 我們不用重新計算.

一般如果你自己寫怎麼寫呢? 假設我那個算獎金的函數寫好了, 就叫computeBonus(姓名). 這樣麼?

class BonusManager {
private final Map<姓名, 獎金> cache = new HashMap<姓名, 獎金>();

獎金 getBonus(姓名 name) {
獎金 bonus = cache.get(name);
if (bonus == null) {
bonus = computeBonus(name); // 危險在這裏!
}
cache.put(name, bonus);
return bonus;
}
}


其實主要邏輯是這樣的. 問題是這個實現不是多線程安全的.
你可以在getBonus()上面加一個synchronized關鍵字, 然後它就線程安全了. 但是, 讓我們假設我們是個跨國知名連鎖大托拉斯, 需要極高極高的併發能力.

聰明如你一定會想到把HashMap改成ConcurrentHashMap.

近了一步了, 但是還不完全. 這是因爲兩個線程可能同時執行if (bonus == null)然後同時去運行computeBonus(). 結果是, 我們可能對某些名字重複運行computeBonus(). 讓我們再假設老闆遇到這種你給公司浪費了資源的情況會炒你魷魚的.

呵呵, 到這裏, 如果你想到了雙檢查鎖機制(所謂的[url=http://en.wikipedia.org/wiki/Double-checked_locking]double-checked locking[/url]), 那算你狠, 遇到你們這種聰明到煞風景的聽衆我真是無話可說, 捲簾朝散, 請假還宮!

而如果你沒想到, 那麼, 恭喜你, 你答對啦! 獎河南雙匯火腿腸一根(94年珍藏版).

我想說的, 其實是, 俺也不知道怎麼寫最好. 所以呀, 最好是不寫. 反正MapMaker都給咱寫好了不是? 人生苦短, 當及時行樂啊, 同志! 有閒功夫多洗幾塊尿布好不好?

那麼, 不賣關子了 (等不及已經看了javadoc文檔的同志們都已經自己寫出來了. 俺再搖頭晃腦故作玄虛恐怕就要吃臭雞蛋, 爛西紅柿伺候咯). 請看. 噹噹噹當 (貝多芬第九交響曲響起)!

ConcurrentMap<名字, 獎金> cache = new MapMaker()
// 如果一個計算一年有效 (嗯, 明年獎金減半!), 那麼設一個過期時間
.expiration(365, TimeUnit.DAYS)
.makeComputingMap(new Function<名字, 獎金>() {
@Override public 獎金 apply(名字 name) {
return computeBonus(name);
}
});


就是這麼簡單. 等等, 我先擺個pose先, 樣子屌不屌?

這裏面唯一需要解釋的就是這個Function. 大家都知道, java是一門羅唆的語言藝術, 講究說學逗唱, 不是, 說錯了, 講究類, 方法, 表達式, 匿名類等等等等, 反正是怎麼寫累人怎麼來. 這裏的目的其實就是告訴makeComputingMap()怎樣"compute". 這個function是個匿名類, 它的apply()函數封裝了怎麼計算這麼一個概念. (哎呀, 手都打酸了)

然後, makeComputingMap()就接手過去, 它內部怎麼搞的咱不關心, 反正我上面想要的功能它全實現了, 這就夠了唄.

行了, 洗尿布去了.

未完待續
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章