繁体   English   中英

Java内存模型和并发

[英]Java Memory Model and Concurrency

给定x86总存储顺序和Java内存模型中的事前发生关系,我们知道编译器不能保证指令的执行顺序。 它可以根据需要重新排序,以提高性能。 鉴于此,我们有:

  • EAXEBX是寄存器的名称
  • [x][y]是存储位置
  • r1r2是局部变量的名称
  • xy是所有线程均可访问的共享变量。 所有变量均为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]

EBXEAX寄存器的可能值是哪些?

int x = 0;
int y = 0;

// Thread 1                         Thread 2
x = 1;                              y = 1; 
r1 = y;                             r2 = x;

r1r2的可能值是多少?

JVM保证写32-bit integeratomic的,因此这不是问题。

您有2个变量x和y在线程之间共享而不synchronization

  1. Thread1突变x并读取y。
  2. 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 他们可能会看到彼此的商店。 我不确定这有多大可能。 它可能需要在存储之后但在加载之前中断一个线程。 如果两个线程都在同一物理核心上运行(超线程),则这种可能性更大


即使xy的存储未对齐并越过高速缓存行,也只能使用01 (C编译器输出和JVM将使变量按照它们的自然对齐方式对齐,这使它成为非问题,但是您可以在asm中执行任何您想做的事情,所以我想我提到了。)发生这种情况是因为两个值仅不同在最低有效字节中。

如果你正在存储32位-1到跨越两个高速缓存行的4个字节,其它线程可以加载的值0x00ffffff0xff0000000x0000ffff0xffff0000 (取决于高速缓存行边界在何处)等,以及通常为00xffffffff (又名-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

并考虑每种可能性的含义,注意大多数以ACCA开头,输出(EAX,EBX)=(1,1)因为分配是在设置EAXEBX之前进行的。 剩下的就是检查另外两种可能性。 CDAB给出(EAX,EBX)=(1,0) ,而ABCD给出(EAX,EBX)=(0,1)

对于Java版本,您声明编译器不保证所执行语句的顺序。 在这种情况下,不难对ABCD进行排序以获得(0,0),(1,0),(0,1)和(1,1)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM