Java 多线程使用'ConcurrentHashMap'迭代器
我需要编写一个缓存的特定实现,它有唯一的键,但可以包含重复的值,例如:Java 多线程使用'ConcurrentHashMap'迭代器,java,caching,concurrency,guava,concurrenthashmap,Java,Caching,Concurrency,Guava,Concurrenthashmap,我需要编写一个缓存的特定实现,它有唯一的键,但可以包含重复的值,例如: "/path/to/one" -> 1 "/path/to/two" -> 2 "/path/to/vienas" -> 1 "/path/to/du" -> 2 该类需要提供非阻塞读取/键查找,但也有典型的创建/更新/删除变异。例如,删除值2将导致 "/path/to/one" -> 1 "/path/to/vienas" -> 1 到目前为止,此缓存的读操作将超过写操作,因
"/path/to/one" -> 1
"/path/to/two" -> 2
"/path/to/vienas" -> 1
"/path/to/du" -> 2
该类需要提供非阻塞读取/键查找,但也有典型的创建/更新/删除变异。例如,删除值2
将导致
"/path/to/one" -> 1
"/path/to/vienas" -> 1
到目前为止,此缓存的读操作将超过写操作,因此写操作性能不是一个问题-只要并发写操作不相互重叠运行即可。条目的总数很可能会少于1000,因此偶尔对值进行迭代仍然是可以承受的
所以我写了这样的东西(伪代码):
因此,如果我用最终ConcurrentHashMap
替换volatile ImmutableMap
的用法,并删除所有synchronized
块,那么相互竞争的并发突变是否可能会使彼此失效?例如,我可以想象对remove
的两个并发调用如何导致竞争条件,从而使第一个remove
的结果完全无效
我所能看到的唯一改进是,通过使用最终ConcurrentHashMap
和保持同步
,我至少可以避免不必要的数据复制
这有意义吗?或者也许我在这里忽略了什么?有人能为这个解决方案推荐其他替代方案吗?如果您进行了替换,您仍然一次只有一个线程使用给定的迭代器。 该警告意味着两个线程不应使用同一迭代器实例。并不是说两个线程不能同时迭代 您可能遇到的问题是,由于删除操作不能在ConcurrentMap的单个原子操作中完成,因此您可以让一个并发线程以中间状态查看映射:一个值已删除,但另一个值未删除
我不确定这是否会更快,因为您说写性能不是问题,但要避免每次写时都复制映射,可以做的是使用ReadWriteLock保护可变的ConcurrentMap。所有读取仍然是并发的,但是对映射的写入将阻止所有其他线程访问映射。您不必在每次修改时都创建一个新副本。可以同时通过多个迭代器/多个线程对
ConcurrentHashMap
进行变异。只是您不应该将迭代器的单个实例交给多个线程并同时使用它
因此,如果使用ConcurrentHashMap
,则不需要将synchronized
留在那里。正如JB Nizet所指出的,这与当前实现之间的区别在于中间状态的可见性。如果您不关心这一点,那么使用ConcurrentHashMap
将是我的首选,因为实现最简单,您不必担心读写性能
//
// tl;dr all writes are synchronized on a single lock and each
// resets the reference to the volatile immutable map after finishing
//
class CopyOnWriteCache {
private volatile Map<K, V> readOnlyMap = ImmutableMap.of();
private final Object writeLock = new Object();
public void add(CacheEntry entry) {
synchronized (writeLock) {
readOnlyMap = new ImmutableMap.Builder<K, V>()
.addAll(readOnlyMap)
.add(entry.key, entry.value)
.build();
}
}
public void remove(CacheEntry entry) {
synchronized (writeLock) {
Map<K, V> filtered = Maps.filterValues(readOnlyMap, somePredicate(entry));
readOnlyMap = ImmutableMap.copyOf(filtered);
}
}
public void update(CacheEntry entry) {
synchronized (writeLock) {
Map<K, V> filtered = Maps.filterValues(readOnlyMap, somePredicate(entry));
readOnlyMap = new ImmutableMap.Builder<K, V>()
.addAll(filtered)
.add(entry.key, entry.value)
.build();
}
}
public SomeValue lookup(K key) {
return readOnlyMap.get(key);
}
}
iterators are designed to be used by only one thread at a time