为什么 Java 内存模型中允许此行为?

JMM中的因果关系似乎是其中最令人困惑的部分。我有一些关于JMM因果关系的问题,以及并发程序中允许的行为。

据我所知,当前的JMM总是禁止因果循环。(我说的对吗?

现在,根据 JSR-133 文档,第 24 页,图例.16,我们有一个示例,其中:

最初x = y = 0

线程 1:

r3 = x;
if (r3 == 0)
    x = 42;
r1 = x;
y = r1;

线程 2:

r2 = y;
x = r2;

直观地说,似乎是不可能的。但是,它不仅尽可能被提及,而且在JMM中也是“允许的”。r1 = r2 = r3 = 42

对于这种可能性,我无法理解的文件中的解释是:

编译器可以确定分配给的唯一值为 0 和 42。由此,编译器可以推断出,在我们执行时,我们刚刚执行了 42 到 的写入,或者我们刚刚读取并看到了值 42。在任何一种情况下,读取 都可以看到值 42 是合法的。然后可以将其更改为 ;这将允许转换并更早地执行,从而导致有问题的行为。在这种情况下,首先提交写入。xr1 = xxxxr1 = xr1 = 42y = r1y = 42y

我的问题是,它到底是什么样的编译器优化?(我对编译器一无所知。由于 42 只是有条件地编写的,因此当语句得到满足时,编译器如何决定编写 ?ifx

其次,即使编译器做了这种推测优化,然后提交然后最终做出,这难道不是违反因果关系循环,因为现在没有因果区别了吗?y = 42r3 = 42

事实上,在同一文档(第 15 页,图 7)中有一个示例,其中提到了类似的因果循环是不可接受的。

那么,为什么这个执行令在JMM中是合法的呢?


答案 1

如前所述,写入的唯一值是 0 和 42。线程 1:x

r3 = x; // here we read either 0 or 42
if (r3 == 0)
  x = 42;  
// at this point x is definitely 42
r1 = x;

因此,JIT 编译器可以重写为 , 并进一步重写 。关键是,线程 1 将始终无条件地将 42 写入 。该变量实际上是冗余的,可以从机器代码中完全消除。因此,示例中的代码仅给出了从 到 的因果箭头的外观,但详细的分析表明实际上不存在因果关系。令人惊讶的后果是,写到可以提前提交。r1 = xr1 = 42y = 42yr3xyy

关于优化的一般说明:我认为您熟悉从主内存读取时所涉及的性能损失。这就是为什么JIT编译器一心要尽可能地拒绝这样做的原因,在这个例子中,事实证明它实际上不需要读取就可以知道要写什么。xy

关于表示法的一般说明:,,是局部变量(它们可以在堆栈上或在CPU寄存器中);,是共享变量(这些变量位于主内存中)。如果不考虑这一点,这些例子就没有意义了。r1r2r3xy


答案 2

编译器可以执行一些分析和优化,并以 Thread1 的以下代码结束:

y=42; // step 1
r3=x; // step 2
x=42; // step 3

对于单线程执行,此代码等效于原始代码,因此是合法的。然后,如果在步骤 1 和步骤 2 之间执行 Thread2 的代码(这是很有可能的),则 r3 也被分配为 42。

此代码示例的整个思想是演示正确同步的必要性。


推荐