64位OpenJDK 7/8中并发长写的值完整性保证
注意:此问题与volatile,AtomicLong或所描述的用例中的任何感知缺陷无关。
我试图certificate或排除的财产如下:
鉴于以下内容:
- 最近的64位OpenJDK 7/8(最好7位,但8位也有帮助)
- 多处理英特尔基础系统
- 一个非易失性的长原始变量
- 多个不同步的mutator线程
- 一个不同步的观察者线程
是否始终保证观察者会遇到由变异器线程写的完整值,或者是撕裂危险的单词?
JLS:不确定
此属性对于32位基元和64位对象引用是存在的,但是对于long和double,JLS不保证:
17.7。 双primefaces和非primefaces的非primefaces处理:
出于Java编程语言存储器模型的目的,对非易失性long或double值的单次写入被视为两个单独的写入:每个32位一半写入一次。 这可能导致线程从一次写入看到64位值的前32位,而从另一次写入看到第二次32位的情况。
但是抱着你的马:
[…]为了效率,这种行为是特定于实现的; Java虚拟机的实现可以自由地以primefaces方式或分两部分执行对long和double值的写入。 鼓励实现Java虚拟机以避免在可能的情况下拆分64位值。 […]
因此,JLS 允许 JVM实现拆分64位写入,并鼓励开发人员相应地进行调整,但也鼓励 JVM实现者坚持使用64位写入。 我们还没有回答最新版本的HotSpot。
HotSpot JIT:谨慎的乐观
由于单词撕裂最有可能发生在紧密循环和其他热点的范围内,我试图分析JIT编译的实际汇编输出。 简而言之:需要进一步测试,但我只能在long上看到primefaces64位操作。
我使用了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; } }
我确保始终使用大于MAX_INT(1e12到1e12 + 1e5)的值,并重复操作足够的次数(1e5)以触发JIT。
编译后,我用hdis执行Counter.main(),如下所示:
java -XX:+UnlockDiagnosticVMOptions \ -XX:PrintAssemblyOptions=intel \ -XX:CompileCommand=print,Counter.put \ Counter
由JIT为Counter.put()生成的程序集如下(为方便起见,添加了十进制行数):
01 # {method} 'put' '(J)V' in 'Counter' 02 ⇒ # parm0: rsi:rsi = long 03 # [sp+0x20] (sp of caller) 04 0x00007fdf61061800: sub rsp,0x18 05 0x00007fdf61061807: mov QWORD PTR [rsp+0x10],rbp ;*synchronization entry 06 ; - Counter::put@-1 (line 15) 07 0x00007fdf6106180c: movabs r10,0x7d6655660 ; {oop(a 'java/lang/Class' = 'Counter')} 08 ⇒ 0x00007fdf61061816: add QWORD PTR [r10+0x70],rsi ;*putstatic counter 09 ; - Counter::put@5 (line 15) 10 0x00007fdf6106181a: add rsp,0x10 11 0x00007fdf6106181e: pop rbp 12 0x00007fdf6106181f: test DWORD PTR [rip+0xbc297db],eax # 0x00007fdf6cc8b000 13 ; {poll_return}
有趣的线条标有’⇒’。 如您所见,使用64位寄存器( rsi )在四字(64位)上执行添加操作。
我还试图通过在’long counter’之前添加一个字节类型的填充变量来查看字节对齐是否存在问题。 assembly输出的唯一区别是:
之前
0x00007fdf6106180c: movabs r10,0x7d6655660 ; {oop(a 'java/lang/Class' = 'Counter')}
后
0x00007fdf6106180c: movabs r10,0x7d6655668 ; {oop(a 'java/lang/Class' = 'Counter')}
两个地址都是64位对齐的,而’movabs r10,…’调用使用的是64位寄存器。
到目前为止,我只测试了添加。 我假设减法行为类似。
其他操作,如按位操作,赋值,乘法等仍有待测试(或由熟悉HotSpot内部的人员确认)。
口译员:不确定
这让我们得到了非JIT场景。 让我们反编译Compiler.class:
$ javap -c Counter [...] static void put(long); Code: 0: getstatic #8 // Field counter:J 3: lload_0 4: ladd 5: putstatic #8 // Field counter:J 8: return [...]
…我们将对第7行的’ladd’字节码指令感兴趣。 但是,到目前为止,我无法将其追溯到特定于平台的实现。
感谢您的帮助!
事实上,你已经回答了自己的问题。
在64位HotSpot JVM上没有double
和long
“非primefaces处理” ,因为
- HotSpot使用64位寄存器来存储64位值( x86_64.ad与x86_32.ad )。
- HotSpot在64位边界上对齐64位字段( universe.inline.hpp )
VNA05-J。 在读取和写入64位值时确保primefaces性
….
VNA05-EX1:对于保证64位long和double值作为primefaces操作进行读写的平台,可以忽略此规则。 但请注意,此类保证不能跨不同平台移植。
上面的链接在安全性的上下文中讨论了这个问题,并且似乎表明在64位平台上你确实可以假设长的赋值是primefaces的。 32位系统在服务器环境中变得越来越少,所以它并不是一个奇怪的假设。 请注意,exception对哪些平台进行此保证有点模糊,并且没有明确声明64位英特尔上的64位openjdk就好了。