注意:此问题与volatile,AtomicLong或所描述的用例中的任何感知缺陷无关.
鉴于以下内容:
- 最近的64位OpenJDK 7/8(最好7位,但8位也很有帮助)
- 多处理英特尔基础系统
- 非易失性长原始变量
- 多个不同步的mutator线程
- 一个不同步的观察者线程
观察者是否始终保证会遇到由变异线程写的完整值,或者是撕裂危险的单词?
此属性对于32位基元和64位对象引用是存在的,但是对于long和double,JLS不保证:
17.7.非原子对double和long的处理:
出于Java编程语言内存模型的目的,对非易失性long或double值的单次写入被视为两个单独的写入:每个32位一半写入一次.这可能导致线程从一次写入看到64位值的前32位,而从另一次写入看到第二次32位的情况.
但是抱着你的马:
[...]为了效率,这种行为是特定于实现的; Java虚拟机的实现可以自由地以原子方式或分两部分执行对long和double值的写入.鼓励Java虚拟机的实现避免在可能的情况下拆分64位值.[...]
因此,JLS 允许 JVM实现拆分64位写入,并鼓励开发人员相应地进行调整,但也鼓励 JVM实现者坚持使用64位写入.我们还没有回答最新版本的HotSpot.
由于单词撕裂最有可能发生在紧密循环和其他热点的范围内,我试图分析JIT编译的实际汇编输出.长话短说:需要进一步测试,但我只能在long上看到原子64位操作.
我使用了hdis,一个OpenJDK的反汇编插件.在我老化的OpenJDK 7u25版本中构建并安装了插件之后,我开始编写一个简短的程序:
public class Counter {
static long counter = 0;
public static void main(String[] _) {
for (long i = (long)1e12; i < (long)1e12 + 1e5; i++)
put(i);
System.out.println(counter);
}
static void put(long v) {
counter += v;
}
}
Run Code Online (Sandbox Code Playgroud)
我确保始终使用大于MAX_INT(1e12到1e12 + 1e5)的值,并重复操作足够的次数(1e5)以触发JIT.
编译后,我用hdis执行Counter.main(),如下所示:
java -XX:+UnlockDiagnosticVMOptions \
-XX:PrintAssemblyOptions=intel \
-XX:CompileCommand=print,Counter.put …Run Code Online (Sandbox Code Playgroud) 问题是64位加载/存储操作何时被认为是原子操作.