Mid*_*int 14 java concurrency multithreading 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);
}
}
Run Code Online (Sandbox Code Playgroud)
处理请求时,每个线程将使用getConfig()函数从存储中请求使用配置.但是,定期(最有可能每隔几天),使用swapConfigs()函数更新和交换配置.调用swapConfigs()的代码不会保留对它传入的Map的引用,因为它只是解析配置文件的结果.
volatile
商店实例变量仍然需要关键字吗?volatile
关键字带来任何潜在的性能瓶颈,我应该知道的或可避免因为读取的速度大大超过写入的速度?非常感谢,
Pét*_*rök 11
由于更改引用是一个原子操作,因此即使丢弃,也不会有一个线程修改引用,另一个线程看到垃圾引用volatile
.但是,新映射可能无法立即显示某些线程,因此可能会无限期地(或永久地)从旧映射中继续读取配置.所以保持volatile
.
正如@BeeOnRope在下面的评论中指出的那样,有更强的理由可以使用volatile
:
"非易失性写入[...]不会在写入和后续读取之间建立发生在之前的关系,这会看到写入的值.这意味着线程可以看到通过实例变量发布的新映射,但是这个新的地图尚未完全构建.这不是直观的,但它是内存模型的结果,它发生在真实的单词中.对于要安全发布的对象,它必须写入
volatile
,或使用少数其他技术
由于您很少更改该值,因此我认为volatile
不会导致任何明显的性能差异.但无论如何,正确的行为胜过表现.
Bee*_*ope 11
不,这不是没有易失性的线程安全,即使除了看到陈旧值的问题.即使没有对地图本身的写入,并且引用分配是原子的,新的Map<>
也没有安全地发布.
对于要安全发布的对象,必须使用某种机制将其传递给其他线程,该机制要么在对象构造,参考发布和参考读取之间建立先发生关系,要么必须使用少数较窄的方法.保证安全发布:
这两种出版物特定方式都不适用于您,因此您需要使用volatile来建立之前发生的事情.
以下是此推理的较长版本,包括指向JLS的链接以及如果您不安全发布可能会发生的一些实际情况示例.
有关安全发布的更多详细信息,请参阅JCIP(强烈推荐)或此处.
你的代码很好.您需要volatile
,否则您的代码将是100%线程安全的(更新引用是原子的),但是更改可能对所有线程都不可见.这意味着一些线程仍将看到旧值store
.
volatile
在你的例子中,这是必须的.你可能会考虑AtomicReference
,但在你的情况下它不会给你更多.
您无法交换性能的正确性,因此您的第二个问题并不真正有效.它会产生一些性能影响,但可能只是在更新期间,这种情况很少发生,正如您所说.基本上JVM将通过"刷新"来确保所有线程都可以看到更改,但之后它将可以作为任何其他本地变量访问(直到下次更新).
顺便说一下,我喜欢Config
类是不可变的,请考虑不可变的Map
实现以防万一.