[英]Java Memory Model and Concurrency
给定x86
总存储顺序和Java内存模型中的事前发生关系,我们知道编译器不能保证指令的执行顺序。 它可以根据需要重新排序,以提高性能。 鉴于此,我们有:
EAX
, EBX
是寄存器的名称 [x]
, [y]
是存储位置 r1
和r2
是局部变量的名称 x
, y
是所有线程均可访问的共享变量。 所有变量均为32位整数。 所以我有两类问题试图确定可能的输出:
[x] == [y] == 0 // the address space of [x] and [y] are 0.
// Thread 1 Thread 2
MOV [x] <- 1 MOV [y] <- 1
MOV EAX <- [y] MOV EBX <- [x]
EBX
和EAX
寄存器的可能值是哪些?
int x = 0;
int y = 0;
// Thread 1 Thread 2
x = 1; y = 1;
r1 = y; r2 = x;
r1
和r2
的可能值是多少?
JVM
保证写32-bit integer
是atomic
的,因此这不是问题。
您有2个变量x和y在线程之间共享而不synchronization
。
Thread1
突变x并读取y。 Thread2
y突变并读取x。 因此, thread1
可以看到旧值y(1或0), thread2
可以看到旧值x(1,0)。
这意味着您可以获得(eax,ebx)的所有四种可能的组合:(0,0)(0,1)(1,0)(1,1)
x86具有严格排序的内存模型,但仍允许StoreLoad重新排序 。
Jeff Preshing的博客文章: 法案中涉及的内存重新排序正是使用那对存储-然后-加载序列作为测试用例,以证明真正可以在真实硬件上观察到重新排序。 他拥有源代码和所有内容。
请注意,每个线程都有其自己的体系结构状态(包括所有寄存器)。 因此,线程1的EAX与线程2的EAX不同。 在thread2中使用EBX仅使谈论变得更容易,与可以实现的POV没有任何区别。
无论如何,两个寄存器都可以以0结尾。这种情况很少发生,但是可以这样,因为每个线程的存储都可以延迟(在存储缓冲区中或其他任何方式),直到另一个线程的负载选择了一个值。 将此设置为合法状态可使CPU主动使用预取的数据来满足负载并缓冲存储,以使它们在退休时可能不会立即在全局上可见。 (“退休”表示运行该指令的线程的体系结构状态(包括EIP)已移至下一条指令,并且已提交效果。)
一旦尘埃落定,其他可能性始终包括两个全局变量均为1
。 每个线程的寄存器中所有4种可能的零值和1都是可能的,包括1
。 他们可能会看到彼此的商店。 我不确定这有多大可能。 它可能需要在存储之后但在加载之前中断一个线程。 如果两个线程都在同一物理核心上运行(超线程),则这种可能性更大 。
即使x
和y
的存储未对齐并越过高速缓存行,也只能使用0
和1
。 (C编译器输出和JVM将使变量按照它们的自然对齐方式对齐,这使它成为非问题,但是您可以在asm中执行任何您想做的事情,所以我想我提到了。)发生这种情况是因为两个值仅不同在最低有效字节中。
如果你正在存储32位-1
到跨越两个高速缓存行的4个字节,其它线程可以加载的值0x00ffffff
或0xff000000
, 0x0000ffff
或0xffff0000
(取决于高速缓存行边界在何处)等,以及通常为0
或0xffffffff
(又名-1
)。
回复:Java。 我还没有阅读过Java内存模型。 其他答案是说它甚至允许编译时重新排序(例如c ++ 11的std :: atomic规则 )。 即使没有,即使没有完整的内存屏障,也会发生StoreLoad重新排序。 因此, 所有四个结果都是可能的 。
即使您的JVM在x86 CPU(而不是像ARM这样的弱排序硬件)上运行,也是如此。
对另一个问题的这个答案也许可以揭示为什么在x86上存在LFENCE / SFENCE,即使在大多数情况下它们都不工作。 (即,当不使用movnt
或弱排序的内存区域(例如USWC视频内存)时)。
或者,只需阅读Jeff Preshing的其他博客文章,以了解有关内存排序的更多信息。 我发现它真的有用自己。
我们可以简单地标记如下语句:
A) [x] <- 1 C) [y] <- 1
B) EAX <- [y] D) EBX <- [x]
我们知道A在B之前,C在D之前,因此只需将C和D插入所有可能的排列中的AB中即可:
CDAB
CADB
CABD
ACDB
ACBD
ABCD
并考虑每种可能性的含义,注意大多数以AC
或CA
开头,输出(EAX,EBX)=(1,1)
因为分配是在设置EAX
和EBX
之前进行的。 剩下的就是检查另外两种可能性。 CDAB
给出(EAX,EBX)=(1,0)
,而ABCD
给出(EAX,EBX)=(0,1)
。
对于Java版本,您声明编译器不保证所执行语句的顺序。 在这种情况下,不难对A
, B
, C
和D
进行排序以获得(0,0),(1,0),(0,1)和(1,1)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.