gpt4 book ai didi

java - 64 位 OpenJDK 7/8 中并发长写入的值完整性保证

转载 作者:塔克拉玛干 更新时间:2023-11-03 03:21:33 26 4
gpt4 key购买 nike

注意:此问题与 volatile、AtomicLong 或所描述用例中的任何感知缺陷无关。

我要证明或排除的性质如下:

Given the following:

  • a recent 64-bit OpenJDK 7/8 (preferably 7, but 8 also helpful)
  • a multiprocessing Intel-base system
  • a non-volatile long primitive variable
  • multiple unsynchronized mutator threads
  • an unsynchronized observer thread

Is the observer always guaranteed to encounter intact values as written by a mutator thread, or is word tearing a danger?

JLS:不确定

此属性存在于 32 位基元和 64 位对象引用中,但 JLS 不保证 long 和 double:

17.7. Non-atomic Treatment of double and long:
For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64-bit value from one write, and the second 32 bits from another write.

但别急:

[...] For efficiency's sake, this behavior is implementation-specific; an implementation of the Java Virtual Machine is free to perform writes to long and double values atomically or in two parts. Implementations of the Java Virtual Machine are encouraged to avoid splitting 64-bit values where possible. [...]

因此,JLS 允许 JVM 实现拆分 64 位写入,并鼓励开发人员进行相应调整,同时也鼓励 JVM 实现者坚持使用 64 位写入。我们还没有最新版本的 HotSpot 的答案。

HotSpot JIT:谨慎乐观

由于单词撕裂最有可能发生在紧密循环和其他热点的范围内,因此我尝试分析 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;
}
}

我确保始终使用大于 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 位)上执行的。

我还尝试通过在“长计数器”之前添加一个字节类型的填充变量来查看字节对齐是否是一个问题。汇编输出的唯一区别是:

之前

    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”字节码指令感兴趣。但是,我一直无法 trace it through到目前为止,特定于平台的实现。

感谢您的帮助!

最佳答案

事实上,您已经回答了自己的问题。

在 64 位 HotSpot JVM 上没有对 doublelong 的“非原子处理”,因为

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

关于java - 64 位 OpenJDK 7/8 中并发长写入的值完整性保证,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25173208/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com