`ConcurrentHashMap`迭代器的多线程用法

min*_*das 5 java concurrency caching concurrenthashmap guava

我需要编写一个特定的缓存实现,它具有唯一的键,但可以包含重复的值,例如:

 "/path/to/one" -> 1
 "/path/to/two" -> 2
 "/path/to/vienas" -> 1
 "/path/to/du" -> 2
Run Code Online (Sandbox Code Playgroud)

该类需要提供非阻塞读取/密钥查找,但也有典型的创建/更新/删除更改器.例如,2应该删除值

"/path/to/one" -> 1
"/path/to/vienas" -> 1
Run Code Online (Sandbox Code Playgroud)

到目前为止,对此缓存的读取将超过写入,因此写入性能不是问题 - 只要并发写入不会在彼此之上运行.条目的总数很可能小于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);
   }
}
Run Code Online (Sandbox Code Playgroud)

在写完上述内容之后,我意识到ConcurrentHashMap它还提供了非阻塞读取,这会使我的所有努力都毫无意义,但是在它的Javadoc中有一个声明引起了人们的注意:

iterators are designed to be used by only one thread at a time
Run Code Online (Sandbox Code Playgroud)

所以,如果我取代的用法volatile ImmutableMapfinal ConcurrentHashMap并删除所有synchronized块,是有可能,竞争并发存取器将无效对方?例如,我可以想象两个并发调用remove将如何导致竞争条件,完全使第一个结果无效remove.

我能看到的唯一改进是,通过使用final ConcurrentHashMap 离开synchronized,我至少可以避免不必要的数据复制.

这有意义 - 或者我在这里可以忽略一些东西?任何人都可以建议其他替代方案吗?

JB *_*zet 5

如果你做了这个替换,你仍然只有一个线程一次使用给定的迭代器.警告意味着两个线程不应使用相同的Iterator实例.不是两个线程不能同时迭代.

您可能遇到的问题是,由于删除操作无法在ConcurrentMap的单个原子操作中完成,因此您可以让并发线程以中间状态查看映射:已删除一个值但不删除另一个值.

我不确定这会更快,因为你说写性能不是问题,但你可以做的就是在每次写入时避免映射的副本,就是使用一个保护可变ConcurrentMap的ReadWriteLock.所有读取仍然是并发的,但是对映射的写入将阻止所有其他线程访问映射.而且每次修改时都不必创建新的副本.