Problem
下面程式碼是相當常見的lazy-instantiation。但在多執行緒存取時,會針對某一相同的key產生多個不同instance。也許最後在map中僅存著一份instance,但如果CreateCache動作耗時或有其它沒預料到的side-effect呢?
private volatile Map<String, Object> mCacheMap= new ConcurrentHashMap<String, Object>(); public Object getCache(String key){ if(!mCacheMap.contains(key)){ mCacheMap.put(key, createCache(key)); } return mCacheMap.get(key); }
最常見的解法是在method上增加synchronized,
public synchronized Object getCache(String key){ // ... }
或在method內增加synchronized block,
public Object getCache(String key){ synchronized(mCacheMap){ // ... } }
上面兩種方式,都屬於不管怎樣就是做同步的控管。但我們只需要針對key相同的情形做控管,才能獲得比較好的效能。
How to?
String的intern method,可以讓你相同字串但不同物件回傳相同的instance,所以我們“也許”可以寫成這樣:
public Object getCache(String key){ synchronized(key.intern()){ // ... } }
但對沒使用過的東西,我無法放100萬個心下來,所以稍微google了這method的資訊。赫然發現,許多的人都不推使用String intern()。觀看intern裡面的實做,最後會呼叫到JVM的native code,因此這實做是綁在JVM上的。詳細可以看這篇,還包含了效能測試結果。
總結裡面的重點是:
- intern建立了一個String Pool。
- Java6 Pool存在PermGen中,必須將PermGen調大。
- Java7與8 Pool存在Heap中,效能會受GC與預設pool size影響。
- 要用intern就是要去tune參數。
如果嫌麻煩不想tune JVM參數,裡面也提到自行實做此String pool。而google guava也做了這樣的東西:
private Interner<String> mStringInterer = Interners.newWeakInterner(); public Object getCache(String key){ synchronized(mStringInterer.intern(key)){ // ... } }
它是thread-safe的,你也可以根據自己的需求選擇要使用Weak Reference還是Strong Reference。
留言
張貼留言