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

时间:2011-08-25 07:51:18

标签: java multithreading concurrency volatile

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

我的项目代码类似于以下代码:

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<String, Config> store = new HashMap<String, Config>();

    public void swapConfigs(HashMap<String, Config> newConfigs) {
        this.store = newConfigs;
    }

    public Config getConfig(String name) {
        return this.store.get(name);
    }

}

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

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

非常感谢,

4 个答案:

答案 0 :(得分:11)

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

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

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

这两种出版物特定方式都不适用于您,因此您需要使用volatile来建立先前发生的事情。

以下是此推理的longer version,包括指向JLS的链接以及如果您不安全发布可能会发生的一些现实事件示例。

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

答案 1 :(得分:9)

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

更新

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

  

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

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

答案 2 :(得分:9)

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

在你的例子中,volatile是强制性的。你可能会考虑AtomicReference,但在你的情况下它不会给你更多的东西。

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

BTW我喜欢Config类是不可变的,为了以防万一,请考虑不可变的Map实现。

答案 3 :(得分:1)

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