JVM 的底层行为仅针对 volatile 变量进行保证。即使在一个线程完成对
b = a;
语句的评估后,两个单独的线程可能会访问变量 'a' 的不同值。JVM 仅保证对 volatile 变量的访问是序列化的,并具有 Happens-Before 语义。这意味着在两个不同的线程中执行
b = a;
的结果(在 "volatile" 值为 'a' 的情况下)是不确定的,因为 JVM 只表示对 'b' 的存储是序列化的,它没有保证哪个线程具有优先权。
更准确地说,这意味着 JVM 将变量 'b' 视为具有自己的锁;每次只允许一个线程读取或写入 'b';而此锁仅保护对 'b' 的访问,而不保护其他任何内容。
现在,这在不同的JVM下有不同的含义,在不同机器架构上实现此锁定可能会导致应用程序运行时行为迥异。您可以信任的唯一保证是Java参考手册所说的,“一个字段可以被声明为volatile,此时Java内存模型确保所有线程都看到一个变量的一致值。”如果需要进一步了解,请参阅Dennis Byrne出色的article,以了解不同JVM实现如何处理此问题。
发生之前的语义在提供的示例中并不是非常有趣,因为整数原语并没有提供很多机会进行指令重排序,而volatile部分旨在弥补这种情况。一个更好的例子是这个:
private AnObjectWithAComplicatedConstructor _sampleA;
private volatile AnObjectWithAComplicatedConstructor _sampleB;
public void getSampleA() {
if (_sampleA == null) {
_sampleA = new AnObjectWithAComplicatedConstructor();
}
return _sampleA;
}
public void getSampleB() {
if (_sampleB == null) {
_sampleB = new AnObjectWithAComplicatedConstructor();
}
return _sampleB;
}
在这个例子中,'_sampleA'字段存在一个严重的问题;在多线程情况下,'_sampleA'可能正在一个线程中被初始化的同时,另一个线程尝试使用它,导致各种零散和非常难以复制的错误。为了看到这一点,请考虑线程X执行getSampleA()中的new字节码指令语句,然后将(尚未初始化)结果存储在字段'_sampleA'中。现在JVM暂停线程X,线程Y开始执行getSampleA()并看到'_sampleA'不为空;然后返回未初始化的值,并且线程Y现在开始调用生成的实例上的方法,导致各种问题;当然,这些问题只会在生产环境中,在奇怪的时间和重负载下出现。
对于字段_sampleB的最坏情况是它可能有多个线程初始化单个实例;其中除一个之外的所有实例最终都将被丢弃。像这样的代码应该包装在“synchronized”块中,但是volatile关键字也可以解决问题,因为它要求最终存储在'_sampleB'中的值具有Happens-Before语义,这意味着等号右侧的内容在执行等号左侧的内容时已经完成。
a
不参与happens-before。所以我认为不是这样的。 - user166390