2013-07-01 182 views
4

我有一個高度併發的應用程序,它利用文件系統上的資源。兩個線程同時訪問同一資源的機會相當小,但如果發生這種情況,應用程序可能會顯示有線行爲。帶弱密鑰的併發映射

每個資源都可以通過String座標矢量進行映射(捆綁在類ResourceIdentifier中)。在我目前的解決方案,我創造了這樣資源標識符的ConcurrentMap收集由線程使用的時候訪問資源監視器:(正確ResourceIdentifier覆蓋equalshashCode

ConcurrentMap<ResourceIdentifier, ResourceIdentifier> concurrentMap 
    = new ConcurrentHashMap<>(); 

public Object aquireMonitor(ResourceIdentifier resourceIdentifier) { 
    concurrentMap.putIfAbsent(resourceIdentifier, resourceIdentifier); 
    return concurrentMap.get(resourceIdentifier); 
} 

當資源被訪問時,我同步由aquireMonitor返回的監視器對象的訪問權限。就我所瞭解的ConcurrentHashMap的實現而言,這並不是必須阻止所有線程(爲了理解實現,我讀了this blog article),並且我的應用程序可以高興地運行,而沒有併發訪問以前引入醜陋的資源之一的危險在非常罕見的情況下出現錯誤。

但是:我的應用程序管理大量資源,concurrentMap隨運行時增長。這就是爲什麼我現在嘗試弱引用語義添加到我的應用程序(通過使用番石榴):

ConcurrentMap<ResourceIdentifier, ResourceIdentifier> concurrentMap 
    = new MapBuilder().weakValues().weakKeys() 
    .concurrencyLevel(CONCURRENCY_LEVEL).makeMap(); 

public Object aquireMonitor(ResourceIdentifier resourceIdentifier) { 
    ResourceIdentifier monitor; 
    do { 
    concurrentMap.putIfAbsent(resourceIdentifier, resourceIdentifier); 
    monitor = concurrentMap.get(resourceIdentifier); 
    } while(monitor == null); 
    return monitor; 
} 

CONCURRENCY_LEVEL當然是一個靜態字段。

我的想法是這樣的:每當監視器仍在被另一個線程使用時,它當然會持有對此監視器的(強)引用。因此,ConcurrentMap中的條目不會被垃圾收集,並且可以保證當兩個線程想要訪問相同的資源時共享監視器。 (環路地址的putIfAbsentget的通話之間可能的垃圾收集。)

然而,MapMaker.weakKeys打破了條目由equals發現和使用身份,而不是合同。

現在我想知道:有人知道該從哪裏出發嗎?或者這種方法反正是一個壞主意?作爲一個側面問題:如果我只使用weakValues,整個條目是否會從地圖上刪除?或者,地圖總是會有其他重要參考?感謝幫助!

PS:我的第一個猜測是我應該從地圖遷移到緩存。這可能是最好的解決方案嗎?我以前從未使用過番石榴,但現在我發現對緩存的關鍵比較有同樣的限制。

PPS:我無法在文件系統上創建鎖定。 (不是我的電話。)

回答

4

您將需要弱鍵和值的引用。

我建議您切換到高速緩存,或限制的是,您切換到一個ConcurrentMapSoftReferences - 氣相色譜很熱心收集弱引用,所以他們是不是真的適合高速緩存,而它的延遲收集軟參考資料,但仍不允許它們導致OutOfMemoryError。要實現軟參考同時映射,您可以創建一個ConcurrentMap包裝,例如,

class SoftConcurrentMap<K, V> extends ConcurrentHashMap<SoftReference<K>, SoftReference<V>> { 
    ConcurrentHashMap<SoftReference<K>, SoftReference<V>> map = new ConcurrentHashMap<>(); 

    V public void get(Object key) { 
     SoftReference<V> value = map.get(new SoftRefrence(key)); 
     if(value != null && value.get() != null) { 
      return value.get(); 
     } else { 
      map.remove(new SoftReference(key)); 
      return null; 
     } 
    } 

    V put(K key, V value) { 
     SoftReference<V> oldValue = map.put(new SoftReference(key), new SoftReference(value)); 
     return oldValue == null ? null : oldValue.get(); 
    } 
} 

等。這是很多方法,所以我建議你使用類似EHCache的東西。

+0

但是這個實現是否會從映射中移除實際存儲在其中的'Entry'對象?我認爲它會刪除條目的值,並保持地圖的污染。 –

+1

@raphw你有兩種選擇:懶惰地通過'get'和'contains'方法刪除舊條目(參見我的'get'方法舉例),或者通過[ReferenceQueue]熱切刪除舊條目(http:// docs .oracle.com/javase/6/docs/api/java/lang/ref/ReferenceQueue.html) –

0

我發現了另一個整潔的解決方案,我實際上實現了。也許這太容易想到它在開始:

ConcurrentMap<ResourceIdentifier, ResourceIdentifier> concurrentMap 
    = new MapBuilder().weakValues() 
    .concurrencyLevel(CONCURRENCY_LEVEL).makeMap(); 

public Object aquireMonitor(ResourceIdentifier resourceIdentifier) { 
    ResourceIdentifier monitor; 
    do { 
    concurrentMap.putIfAbsent(resourceIdentifier, new Object()); 
    monitor = concurrentMap.get(resourceIdentifier); 
    } while(monitor == null); 
    return monitor; 
} 

而不使用weakKeys。這像一個魅力。這些鍵不再代表對用作監視器的實際對象的強引用,並且只要沒有線程持有對它們的強引用,地圖的條目就會被垃圾收集。

+0

嗨,while(monitor == null)'loop {do,..}是什麼點?您是否試圖防止您的_create-read-check_ triple操作針對其他線程或GC將刪除條目的競爭條件?在這種情況下,它仍然傾向於在讀取監視器,地圖'get()'和'while()'中檢查它之間的競爭條件。你必須同步整個'aquireMonitor()'或使用Map的'computeIfAbsent()'或者只是'compute()'(自Java 8以來)。 – Espinosa

+0

所以,你要強調的是,Guava的新MapBuilder()。weakValues().makeMap()和Java標準的新ConcurrentHashMap >之間的主要區別在於前者清除了所有條目的映射,即鍵和值,如果值弱參考被GC清除?後者除非手動清除,否則保留條目。 – Espinosa