我需要编写一个缓存的特定实现,它具有唯一键但可以包含重复值,例如:
"/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,因此偶尔迭代值仍然是可以承受的。
所以我写了这样的东西(伪代码):
//
// 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);
}
}
写完上面的内容后,我意识到ConcurrentHashMap
它还提供了非阻塞读取,这将使我的所有努力都变得毫无意义,但是它的 Javadoc 中有一个声明引起了人们的注意:
iterators are designed to be used by only one thread at a time
因此,如果我替换 with 的使用volatile ImmutableMap
并final ConcurrentHashMap
删除所有synchronized
块,竞争的并发突变器是否有可能相互失效?例如,我可以想象两个并发调用如何remove
导致竞争条件,使第一个的结果完全无效remove
。
我能看到的唯一改进是,通过使用final ConcurrentHashMap
和保持synchronized
原样,我至少可以避免不必要的数据复制。
这有意义吗 - 或者我在这里忽略了一些东西?任何人都可以为此解决方案提出其他替代方案吗?