我正在試圖製作一個使用set-in-a-map線程安全的類。我不確定what
特別需要同步。Java Collection-Within-Collection併發性
該地圖被定義爲類似於Map<Class<K>, Set<V>> map;
的東西。以下是地圖被在實現內部使用的方式減少:
public void addObject(K key, V object) {
getSet(key).add(object);
}
public void removeObject(K key, V object) {
getSet(key).remove(object);
}
public void iterateObjectsInternally(K key, Object... params)
{
for (V o : getSet(key)) {
o.doSomething(params);
}
}
private Set<V> getSet(K key) {
if (!map.containsKey(key)) {
map.put(key, new Set<V>());
}
return map.get(key);
}
問題
至於使用map
本身去,唯一的併發問題,我看是在getSet(K)
,其中線程上下文可以在containsKey
和put
之間切換。在這種情況下,可能出現以下情況:
[Thread A] map.containsKey(key) => returns false
[Thread B] map.containsKey(key) => returns false
[Thread B] map.put(key, new Set<V>())
[Thread B] map.get(key).add(object)
[Thread A] map.put(key, new Set<V>()) => Thread A ovewrites Thread B's object [!]
[Thread B] map.get(key).add(object)
現在,我目前使用此實現定期HashMap
。而且,如果我正確的話,使用Collection.synchronizedMap()
或ConcurrentHashMap
只會解決方法級別的併發問題。也就是說,方法將以原子方式執行。這些都沒有提到方法之間相互作用的方式,所以即使使用並行解決方案,以下情況仍然會發生。
ConcurrentHashMap
確實有,但是,有方法putIfAbsent
。這個缺點是map.putIfAbsent(key, new Set<V>())
陳述會在每次請求時創建一個新的集合。這似乎是一個很大的開銷。
另一方面,僅僅將這兩個語句包裝在同步塊中就足夠了嗎?
synchronized(map) {
if (!map.containsKey(key)) {
map.put(key, new Set<V>());
}
}
有沒有比鎖定整個地圖更好的方法?有沒有辦法只鎖定鍵,以便地圖上的其他值的讀取不會被鎖定?
synchronized(key) {
if (!map.containsKey(key)) {
map.put(key, new Set<V>());
}
}
請注意,密鑰不一定是相同的對象(它們是特別Class<?>
類型),但是通過哈希碼相等。如果同步要求對象地址相等,則同步key
可能無法正常工作。
與套裝
更大的問題問題,我想,如果是一組被正確使用自知。有幾個問題:添加對象,刪除對象和迭代對象。
包裝Collections.synchronizedList
列表是否足以避免addObject
和removeObject
中的併發問題?我假設沒問題,因爲同步封裝將使它們成爲原子操作。
但是,迭代可能是一個不同的故事。對於iterateObjectsInternally
,即使設定是同步的,但它仍然必須保持外部同步:
Set<V> set = getSet(key);
synchronized(set) {
for (V value : set) {
// thread-safe iteration
}
}
然而,這似乎是一個可怕的浪費。如果相反,我們更換簡單地使用CopyOnWriteArrayList
或CopyOnWriteArraySet
作爲定義。由於迭代只會使用數組內容的快照,因此無法從其他線程修改它。另外,CopyOnWriteArrayList
對add和remove方法使用重入鎖,這意味着add和remove同樣也是安全的(因爲它們是同步方法)。 CopyOnWriteArrayList
似乎很有吸引力,因爲內部結構的迭代次數遠遠超過列表中修改的次數。此外,使用複製的迭代器,無需擔心addObject
或removeObject
在另一個線程中搞亂了從iterateObjectInternally
(ConcurrentModificationExceptions
)開始的迭代。
這些並行檢查是否在正確的軌道上和/或足夠嚴格?我是一名有併發編程問題的新手,我可能會錯過某些明顯或過度思考的東西。我知道有幾個類似的問題,但是我的實施看起來不同,不能像我那樣專門提問。
這是一個_extremely_難題。如果我是你,我會從Guava的'SetMultimap'和['Multimaps.synchronizedSetMultimap']開始(http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common /collect/Multimaps.html#synchronizedSetMultimap(com.google.common.collect.SetMultimap)),它只是鎖定每個操作上的所有內容,然後從那裏開始工作。 –