我正在试图制作一个使用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)),它只是锁定每个操作上的所有内容,然后从那里开始工作。 –