【发布时间】:2012-01-19 22:35:39
【问题描述】:
我需要编写一个缓存的具体实现,它具有唯一键但可以包含重复值,例如:
"/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
因此,如果我将 volatile ImmutableMap 的用法替换为 final ConcurrentHashMap 并删除所有 synchronized 块,是否有可能竞争的并发突变器会相互失效?例如,我可以想象对remove 的两个并发调用将如何导致竞争条件,使第一个remove 的结果完全无效。
我能看到的唯一改进是,通过使用final ConcurrentHashMap 和 将synchronized 保持原样,我至少可以避免不必要的数据复制。
这有意义吗 - 或者我在这里忽略了一些东西?任何人都可以为此解决方案提出其他替代方案吗?
【问题讨论】:
标签: java caching concurrency guava concurrenthashmap