Java内存模型和并发

时间:2015-11-29 18:58:50

标签: java multithreading parallel-processing x86 java-memory-model

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

  • 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的可能值是什么?

3 个答案:

答案 0 :(得分:4)

32-bit integer保证atomicJVM,所以这不是问题。

在没有synchronization的线程之间共享2个变量x和y。

  1. Thread1改变x并读取y。
  2. Thread2变异y并读取x。
  3. 因此,thread1可能会看到陈旧的y(1或0)值,而thread2可能会看到陈旧值x(1,0)。

    这意味着您可以获得(eax,ebx)的所有四种可能组合: (0,0) (0,1) (1,0) (1,1)

答案 1 :(得分:4)

x86具有强排序的内存模型,但仍然允许StoreLoad reordering

Jeff Preshing的博客文章:Memory Reordering Caught in the Act,正好使用那对store-then-load序列作为测试用例,以证明在真实硬件上可以观察到重新排序。他有源代码和一切。

请注意,每个线程都有自己的架构状态(包括所有寄存器)。因此,thread1的EAX与thread2的EAX不同。在thread2中使用EBX只会使讨论变得更容易,与可能发生的POV没有任何不同。

无论如何,两个寄存器都可以以0结尾。这很少发生,但它可以,因为每个线程的存储可以被延迟(在存储缓冲区或其他任何),直到其他线程的负载之后选择了一个值。如果这是合法的,那么CPU会积极地使用预取数据来满足负载,并缓冲存储,以便它们在退出时可能不会立即全局可见。 (&#34;退休&#34;表示运行指令的线程的架构状态(包括EIP)已转移到下一条指令,并且提交了效果。)

其他可能性,一旦尘埃落定,总是包括1的全局变量。在每个线程的寄存器中,所有4个可能的零值和一个值都是可能的,包括1。他们可以看到彼此的商店。我不确定这有多大可能;它可能需要一个线程在其存储之后但在其加载之前被中断。如果两个线程都在同一物理核心上运行(超线程),this possibility is much more likely

即使xy的存储未对齐且跨越缓存行,01也是唯一可能的值。 (C编译器输出和JVM会将变量对齐到它们的自然对齐,这使得它不是问题,但你可以在asm中做任何你想要的事情,所以我想我会提到它。)这是因为这两个值仅在最低有效字节中有所不同。

如果要存储跨越两个缓存行的32位-1到4个字节,则另一个线程可以加载值0x00ffffff0xff0000000x0000ffff或{ {1}}等等(取决于缓存行边界的位置),以及通常的0xffff00000(又名0xffffffff)。

re:Java。我还没有读过Java内存模型。其他答案说它甚至允许编译时重新排序(如c++11's std::atomic rules)。即使没有,没有完整的内存屏障,也可能发生StoreLoad重新排序。所以所有四个结果都是可能的

即使您的JVM在x86 CPU(而不是像ARM这样的弱排序硬件)上运行,也是如此。

This answer to another question可能会解释为什么在x86上存在LFENCE / SFENCE,即使它们在大多数情况下都是无操作的。 (即不使用-1或弱序存储区域(如USWC视频存储器)时。)

或者,只需阅读Jeff Preshing的其他博文,了解有关内存排序的更多信息。我发现真的非常有帮助。

答案 2 :(得分:2)

我们可以简单地将语句标记如下:

server: {}

我们知道A出现在B之前,而C出现在D之前,所以只需在所有可能的排列中将C和D插入AB中:

A) [x] <- 1            C) [y] <- 1

B) EAX <- [y]           D) EBX <- [x]

考虑每种可能性的含义,注意大部分以CDAB CADB CABD ACDB ACBD ABCD AC开头,输出CA,因为分配发生在(EAX,EBX)=(1,1)和{{{}之前1}}正在设置中。剩下的就是检查另外两种可能性。 EAX提供EBXCDAB提供(EAX,EBX)=(1,0)

对于Java版本,您声明编译器不保证执行的语句的顺序。在这种情况下,订购ABCD(EAX,EBX)=(0,1)AB来获取(0,0),(1,0)应该不难(0,1)和(1,1)。