在Java中,将对HashMap的引用更改为并发读取是否安全

在Java中,将对HashMap的引用更改为并发读取是否安全,java,multithreading,concurrency,volatile,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,

我希望这不是一个太傻的问题

在我的项目中,我有类似于以下内容的代码:

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);
    }

}
公共类配置存储{
公共静态类配置{
公共最终字符串设置1;
公共最终字符串设置2;
公共最终字符串设置3;
公共配置(字符串设置1、字符串设置2、字符串设置3){
this.setting1=setting1;
this.setting2=setting2;
this.setting3=setting3;
}
}
private volatile HashMap store=new HashMap();
公共void交换配置(HashMap newConfigs){
this.store=newConfigs;
}
公共配置getConfig(字符串名称){
返回此.store.get(名称);
}
}
在处理请求时,每个线程将使用getConfig()函数从存储中请求要使用的配置。但是,使用swapconfig()函数定期(最有可能每隔几天)更新和交换配置。调用swapConfigs()的代码不保留对它传入的映射的引用,因为它只是解析配置文件的结果

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

非常感谢,

因为更改引用是一个原子操作,所以即使您删除
volatile
,也不会让一个线程修改引用,而另一个线程看到垃圾引用。但是,对于某些线程来说,新映射可能不会立即可见,因此可能会无限期(或永远)从旧映射读取配置。因此,保持
不稳定

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

“非易失性写入[…]不要在写入和后续读取之间建立“先发生后读取”关系,以查看写入的值。这意味着线程可以看到通过实例变量发布的新映射,但此新映射尚未完全构建。这不是直观的,但它是内存模型的结果,并且在真实世界中发生。为了安全发布对象,必须将其写入到
volatile
,或者使用一些其他技术


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

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

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

< P> >代码> Value在您的示例中是强制性的。您可能会考虑<代码> AtomicReference <代码>,但是在您的情况下它不会给您任何更多的东西。 你不能用正确性来换取性能,所以你的第二个问题实际上是无效的。它会对性能产生一些影响,但可能只会在更新过程中发生,正如你所说的,更新很少发生。基本上JVM会通过“刷新”来确保所有线程都能看到更改“它是可访问的,但在此之后,它将作为任何其他局部变量进行访问(直到下一次更新)


我们喜欢<代码> CONFIG/<代码>类是不可变的,也请考虑不可变的<代码> map < /COD>实现,以防万一。

< P> <强>不,这不是线程安全< /强>没有波动,甚至除了看过时值的问题之外。即使没有对映射本身的写入,并且引用分配是原子的,新的
map
也没有安全地发布

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

  • 从静态初始值设定项初始化对象引用
  • 将对它的引用存储到最终字段中
这两种特定于出版物的方法都不适用于您,因此您需要volatile来确定之前发生的情况

下面是这一推理的一个例子,包括到JLS的链接,以及一些如果不安全发布可能发生的真实世界事件的示例


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

我之所以不使用ConccurentHashMap,是因为我希望能够在一次操作中完成一个良好配置与另一个良好配置的完全交换。使用ConcurrentHashMap可能意味着我想以某种方式合并配置。@中点,我怀疑这就是原因。+1:无论映射在运行时是否在集合中显式不可变。unmodifiableMap()与否,设置映射后,您应该更改映射的内容。如果您需要能够做到这一点,请使用ConcurrentHashMap并将其设置为最终版本,即使用putAll和keySet().retainAll()更新ConcurrentHashMapI agree with@BeeOnRope:如果没有volatile,代码是线程安全的-不仅一个线程可以看到过时的映射引用,而且还可以看到指向不一致映射的最新引用(也就是说,构造不正确)。Peter在这里打败了Tomasz,但两个答案基本上是相同和正确的。非常感谢。:)更改64位引用真的能保证t吗