gpt4 book ai didi

Java volatile 内存排序及其在 x86-64 上的编译

转载 作者:行者123 更新时间:2023-12-05 00:42:28 25 4
gpt4 key购买 nike

考虑以下简单的 Java 应用程序:

public class Main {
public int a;
public volatile int b;

public void thread1(){
int b;
a = 1;
b = this.b;
}

public void thread2(){
int a;
b = 1;
a = this.a;
}

public static void main(String[] args) throws Exception {
Main m = new Main();
while(true){
m.a = 0;
m.b = 0;
Thread t1 = new Thread(() -> m.thread1());
Thread t2 = new Thread(() -> m.thread2());
t1.start();
t2.start();
t1.join();
t2.join();
}
}
}

问题: 读入局部变量是否会导致 thread1::b = 0thread2::a = 0 ?

我无法从 JMM 的角度证明它不可能发生,所以我开始分析 x86-64 的编译代码。

这是编译器对 thread1thread2 方法的最终结果(与 while 循环无关的代码和 -XX:+PrintAssembly 为简单起见省略):

thread1:

  0x00007fb030dca235: movl    $0x1,0xc(%rsi)    ;*putfield a
0x00007fb030dca23c: mov 0x10(%rsi),%esi ;*getfield b

线程2:

  0x00007fb030dcc1b4: mov     $0x1,%edi
0x00007fb030dcc1b9: mov %edi,0x10(%rsi)
0x00007fb030dcc1bc: lock addl $0x0,0xffffffffffffffc0(%rsp) ;*putfield b
0x00007fb030dcc1c2: mov 0xc(%rsi),%esi ;*getfield a

所以我们这里得到的是 volatile 读取是免费完成的,volatile 写入需要 mfence(或 lock add) 之后。

所以thread1的Store在Load之后仍然可以转发,因此thread1::b = 0thread2::a = 0 是可能的。

最佳答案

是的,您的分析看起来正确。这是 StoreLoad 试金石,只有 一个 面具有 StoreLoad 屏障(如 C++ std::atomic iwth memory_order_seq_cst 或 Java volatile )。两者都需要关闭这种可能性。见 Jeff Preshing 的 Memory Reordering Caught in the Act有关双方都没有这种障碍的情况的详细信息。

a=1b=this.b

StoreLoad 重新排序允许

的有效顺序
   thread1        thread2
b=this.b // reads 0
b=1
a=this.a // reads 0
a=1

(这种困惑的名称是为什么示例和重新排序试金石测试选择像 r0r1 这样的名称作为“寄存器”来讨论线程的加载结果是正常的观察到,与共享变量的名称绝对不同,这使得语句的含义与上下文相关,并且在重新排序图中查看和思考很痛苦。)

So thread1's Store can still be forwarded after the Load and therefore thread1::b = 0 and thread2::a = 0 is possible.

您的意思似乎是“重新排序”,而不是转发。内存排序上下文中的“转发”意味着存储到加载转发(其中负载在全局可见之前从存储缓冲区中提取数据,因此它会立即看到自己的存储,相对于其他事物以不同的顺序比其他线程会)。但是您的两个线程都没有重新加载自己的商店,所以这不会发生。

x86 的内存模型基本上是程序顺序 + 带有存储到加载转发的存储缓冲区,因此 StoreLoad 重新排序是唯一可能发生的类型。

所以是的,这是最接近排除 ra=rb=0 的可能性,同时仍然留有一个窗口让其发生。在强排序 ISA (x86) 上运行,一侧有屏障。

当您在每个线程启动时只进行一次测试时,也不太可能观察到;毫不奇怪,这些执行需要 30 分钟才能在内核之间以足够接近的时间同时发生以观察到这一点。 (更快的测试不是微不足道的,就像第三个线程在测试之间重置事物并唤醒其他两个线程?但是做一些事情让两个线程更有可能同时到达这个代码可能会有很大帮助,比如让它们都旋转等待同一个变量,所以它们可能会在彼此的一百个周期内醒来。 )

关于Java volatile 内存排序及其在 x86-64 上的编译,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73603561/

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