java 中的 volatile HashMap 是否足以满足单个写入器的多个读取器?

is volatile HashMap enough for single writer multiple readers in java?

一次只有一个线程会调用 reload()。 我想确保在任何时候,调用 get 的人都会看到同一张最新地图的内容 实例.

请原谅我对要求的模糊描述。 我想确保,每当线程调用重新加载时,所有 readers 都会从 valid 映射中获取映射条目(引用和内部结构均有效) 实例,无论是旧的还是新的地图实例都可以。但是,如果从损坏的映射结构中获取 return 映射,则不行。

对于初始化,假设 MapCache.map 以某种方式安全初始化。

make 实例变量 map volatile 足以保证吗?

public class MapCache {
  
  private volatile Map<String,String> map;


  public void reload() {
    this.map = newMap();
  }

  public String get(String key) {
    return map.get(key);
  }

  private Map<String,String> newMap(){
    Map<String, String> localMap = new HashMap<>();
    //init localMap;
    return localMap;
  }
}

在 Java 并发实践中,第 52 页,安全发布习语 包括将对它的引用存储到易失性字段或 AtomicReference 中; 这意味着对对象的引用和对象的状态可以同时对其他线程可见。

但在第 325 页,

The atomic array classes provide volatile access semantics to the elements of the array, a feature not available for ordinary arrays—a volatile array has volatile semantics only for the array reference, not for its elements.

易失性数组仅对数组引用具有易失性语义,对其元素没有。 我不太明白这句话。好像和52页的矛盾。那么在更新一个volatile数组的时候,我们能保证其他线程能看到新的数组和数组内容吗?

HashMap 使用数组作为其内部状态,所以 volatile HashMap 是否足以满足我的要求?

transient Node<K,V>[] table;

我在网上搜索了一下,有人说 volatile 有 hasppens-before 效果(Java 并发实践中有介绍)。看起来 每次编写器线程(比如 ThreadA)调用 reload() 并更新易失性映射引用时,ThreadA 看到的映射内部结构对于任何调用 get 访问易失性映射引用的 reader 线程都是可见的。我的理解对吗?这里的一些答案说即使这样也不能保证。

不知道这里的概念是什么?您将新的 hasmap 返回到各个线程?如果只有一个线程将从单个 hashmap 写入和读取,那么就可以了。但是如果你有多个线程 read/writes 在单个 hashmap 上那么你就有问题了。

简而言之,不,volatile 在这里不足以确保在这种情况下“先于发生”排序。

但在这种情况下,我不确定它是否那么重要。
考虑一个线程 A:

while(true) {
    mapCache.reload();
}

还有另一个线程 B:

while(true) {
    System.out.println(mapCache.get(key));
}

无论 reload() 是否同步,您从线程 B 获得的输出取决于 get(String) 实际发生的时间。它可能真的落在旧地图或新地图上。

volatile 不够的原因是因为虽然你的 map 字段中的更新引用将立即对所有线程可见,但你的操作本身更新该字段不是同步的或原子的。

例如,一个线程可能调用 reload() 然后另一个调用 get(String) 但在第一个线程之前 实际上 更改了 [=16] 引用的 Map =] 第二个线程可能已经读取了旧的 map。出于这个原因,您需要同步来阻止线程调用 get,而另一个线程正在使用 reload.

更新地图。

I want to ensure that at any time, callers of get will see the content of the same latest map instance.

...is volatile HashMap enough...?

使map变量volatile不足以确保调用map.get("foobar")的线程将看到其他线程看到的最新值线程 put 进入该键下的地图。事实上,当第一个线程调用 get.

时,它甚至不能保证映射的内部结构看起来是有效的

声明 volatile Map<...> map 不会 使地图不稳定。 Java 中没有 volatile 对象这样的东西。只有变量可以是易变的。 map 变量是 volatile 的事实仅在某些线程时才重要 分配变量(例如,当某个线程调用您的 reload() 方法时。)

由于映射实际上是不可变的,所以在写入 MapCache.map 后不会更新,所以没有问题。

要求的是不应该有数据竞争。当您有 2 个冲突的操作(MapCache.map 字段的 reading/writing)时,您会遇到数据竞争,这些操作不是按发生前关系排序的。

新填充映射的构造(因此将项目添加到桶中)和映射到 MapCache.map 字段的写入之间存在先行边。这是因为程序顺序规则。

当对 MapCache.map 字段的读取看到对该字段的特定写入时,则在该字段的写入和读取之间存在先行边沿。这是由于可变变量规则。

加载 MyCache.map 字段和使用它之间存在先行边(例如,通过访问 Map 的存储桶调用 map.get)。这是因为程序顺序规则。

happens-before 关系是可传递的,因此是 creating/filling 地图和读取地图内容之间的 happens-before 边,因此不存在数据争用。

您对数组内存模型语义的担忧无关紧要,因为映射实际上是只读的。只有当你会写一些并发的 HashMap 时,你才需要担心数组中项目的内存排序影响。