我希望这不是一个愚蠢的问题...
我的项目代码类似于以下代码:
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
关键字是否会引入我应该注意或可以避免的任何潜在性能瓶颈,因为读取速率大大超过写入速率?非常感谢,
答案 0 :(得分:11)
不,这不是线程安全没有易失性,即使除了看到陈旧值的问题。即使没有对地图本身的写入,并且引用分配是原子的,新的Map<>
也没有安全发布。
对于要安全发布的对象,必须使用某种机制将其传递给其他线程,该机制要么在对象构造,参考发布和参考读取之间建立先发生关系,要么必须使用少数较窄的保证发布安全的方法:
这两种出版物特定方式都不适用于您,因此您需要使用volatile来建立先前发生的事情。
以下是此推理的longer version,包括指向JLS的链接以及如果您不安全发布可能会发生的一些现实事件示例。
答案 1 :(得分:9)
由于更改引用是一个原子操作,因此即使丢弃volatile
,也不会有一个线程修改引用,另一个线程会看到垃圾引用。但是,新映射可能无法立即显示某些线程,因此可能会无限期地(或永久地)从旧映射中继续读取配置。所以请保持volatile
。
正如@BeeOnRope在下面的评论中指出的那样,使用volatile
有更强的理由:
“非易失性写入[...]不会在写入和后续读取之间建立发生之前的关系,这会看到写入的值。这意味着一个线程可以看到一个新的通过实例变量发布的地图,但这个新地图尚未完全构建。这不是直观的,但它是内存模型的结果,它发生在真实的单词中。对于要安全发布的对象,它必须写入
volatile
,或使用其他一些技巧。
由于您很少更改该值,我认为volatile
不会导致任何明显的性能差异。但无论如何,正确的行为胜过表现。
答案 2 :(得分:9)
你的代码很好。您需要volatile
,否则您的代码将是100%线程安全的(更新引用是原子的),但是对所有线程的更改可能不是可见。这意味着某些线程仍然会看到 store
的旧值。
在你的例子中,volatile
是强制性的。你可能会考虑AtomicReference
,但在你的情况下它不会给你更多的东西。
您无法交换表现的正确性,因此您的第二个问题并非真正有效。它会产生一些性能影响,但可能只是在更新期间,这种情况很少发生,正如您所说。基本上JVM将通过“刷新”来确保所有线程都可以看到更改,但之后它将可以作为任何其他本地变量访问(直到下次更新)。
BTW我喜欢Config
类是不可变的,为了以防万一,请考虑不可变的Map
实现。
答案 3 :(得分:1)
您是否可以使用ConcurrentHashMap而不是交换整个配置更新哈希映射中受影响的值?