在Java中,同时更改对HashMap读取的引用是安全的

我希望这不是一个太愚蠢的问题……

我的代码中的代码类似于以下代码:

public class ConfigStore { public static class Config { public final String setting1; public final String setting2; public final String setting3; public Config(String setting1, String setting2, String setting3) { this.setting1 = setting1; this.setting2 = setting2; this.setting3 = setting3; } } private volatile HashMap store = new HashMap(); public void swapConfigs(HashMap newConfigs) { this.store = newConfigs; } public Config getConfig(String name) { return this.store.get(name); } } 

处理请求时,每个线程将使用getConfig()函数从存储中请求使用配置。 但是,定期(最有可能每隔几天),使用swapConfigs()函数更新和交换配置。 调用swapConfigs()的代码不会保留对它传入的Map的引用,因为它只是解析配置文件的结果。

  • 在这种情况下,商店实例变量仍然需要volatile关键字吗?
  • volatile关键字是否会引入我应该注意或可以避免的任何潜在的性能瓶颈,因为读取速率大大超过写入速率?

非常感谢,

由于更改引用是一个primefaces操作,因此即使丢弃volatile ,最终也不会有一个线程修改引用,另一个线程会看到垃圾引用。 但是,新映射可能无法立即显示某些线程,因此可能会无限期地(或永久地)从旧映射中继续读取配置。 所以保持volatile

更新

正如@BeeOnRope在下面的评论中指出的那样,使用volatile有更强的理由:

“非易失性写入[…]不会在写入和后续读取之间建立发生在之前的关系,这会看到写入的值。这意味着线程可以看到通过实例变量发布的新映射,但是这个新的map还没有完全构造。这不是直观的,但它是内存模型的结果,它发生在真实的单词中。对于要安全发布的对象,它必须写入volatile ,或者使用少数其他技术。

由于您很少更改该值,因此我认为volatile不会导致任何明显的性能差异。 但无论如何,正确的行为胜过表现。

不,这不是没有易失性的线程安全 ,即使除了看到陈旧值的问题。 即使没有对地图本身的写入,并且引用分配是primefaces的,新的Map<>也没有被安全地发布

对于要安全发布的对象,必须使用某种机制将其传递给其他线程,该机制要么在对象构造,参考发布和参考读取之间建立先发生的关系,要么必须使用少数几种较窄的方法。保证发布安全:

  • 从静态初始化程序初始化对象引用。
  • 将对它的引用存储到最终字段中。

这两种出版物的具体方式都不适用于您,因此您需要使用volatile来确定之前发生的事情。

以下是此推理的较长版本 ,包括指向JLS的链接以及如果您不安全发布可能会发生的一些实际情况示例。

有关安全发布的更多详细信息,请参阅JCIP (强烈推荐)或此处 。

你的代码很好。 您需要volatile ,否则您的代码将是100%线程安全的(更新引用是primefaces的),但是更改可能对所有线程都不可见 。 这意味着一些线程仍然会看到 store的旧值。

在你的例子中,所说的volatile是必须的。 你可能会考虑使用AtomicReference ,但在你的情况下它不会给你任何更多。

您无法交换性能的正确性,因此您的第二个问题并不真正有效。 它会产生一些性能影响,但可能只是在更新期间,这种情况很少发生,正如您所说。 基本上JVM将通过“刷新”来确保所有线程都可以看到更改,但之后它将可以像任何其他本地变量一样访问(直到下次更新)。

顺便说一下,我喜欢Config类是不可变的,请考虑不可变的Map实现以防万一。

您是否可以使用ConcurrentHashMap而不是交换整个配置更新哈希映射中受影响的值?