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上没有doublelong “非primefaces处理” ,因为

  1. HotSpot使用64位寄存器来存储64位值( x86_64.ad与x86_32.ad )。
  2. HotSpot在64位边界上对齐64位字段( universe.inline.hpp )

https://www.securecoding.cert.org/confluence/display/java/VNA05-J.+Ensure+atomicity+when+reading+and+writing+64-bit+values

VNA05-J。 在读取和写入64位值时确保primefaces性

….

VNA05-EX1:对于保证64位long和double值作为primefaces操作进行读写的平台,可以忽略此规则。 但请注意,此类保证不能跨不同平台移植。

上面的链接在安全性的上下文中讨论了这个问题,并且似乎表明在64位平台上你确实可以假设长的赋值是primefaces的。 32位系统在服务器环境中变得越来越少,所以它并不是一个奇怪的假设。 请注意,exception对哪些平台进行此保证有点模糊,并且没有明确声明64位英特尔上的64位openjdk就好了。