我正在尝试创建一个使用 set-within-a-map 线程安全的类。我不确定what
特别需要同步。
该地图被定义为类似于 的东西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
本身而言,我看到的唯一并发问题是 in ,线程上下文可能在和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()
or ConcurrentHashMap
will 只会解决方法级别的并发问题。也就是说,方法将以原子方式执行。这些并没有说明方法之间的交互方式,因此即使使用并发解决方案,以下情况仍然可能发生。
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
这些并发检查是否在正确的轨道上和/或足够严格?我是一个有并发编程问题的新手,我可能遗漏了一些明显的或过度思考的东西。我知道有一些类似的问题,但我的实现似乎足够不同,足以保证像我一样具体地提出问题。