为什么在Java内存模型中允许这种行为?

时间:2012-11-07 14:22:32

标签: java concurrency compiler-optimization java-memory-model causality

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;

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

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

  

编译器可以确定分配给x的唯一值是   从那时起,编译器可以推断出这一点   在我们执行r1 = x的地方,我们刚刚执行了42次写入   x,或者我们刚刚阅读x并看到了值42.在任何一种情况下,它都是   阅读x以查看价值是合法的   42.然后可以将r1 = x更改为r1 = 42;这将允许y = r1转换为y = 42并在之前执行,从而产生。{1}}   有问题的行为。在这种情况下,将提交对y的写入   第一

我的问题是,它真的是什么样的编译器优化? (我是编译器无知的。)由于42是有条件写的,当满足if语句时,编译器如何决定写x

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

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

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

3 个答案:

答案 0 :(得分:6)

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

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

因此,JIT编译器可以将r1 = x重写为r1 = 42,并进一步y = 42。关键是,线程1将始终无条件地将42写入yr3变量实际上是多余的,可以从机器代码中完全消除。因此,示例中的代码仅显示从xy的因果箭头的外观,但详细分析表明实际上没有因果关系。令人惊讶的结果是,y的写入可以提前提交。

关于优化的一般说明:我认为你熟悉从主存储器中读取所涉及的性能惩罚。这就是为什么JIT编译器倾向于拒绝尽可能地执行它,并且在这个示例中,事实证明它实际上不需要读取x以便知道写入{{1}的内容}。

关于符号的一般说明:yr1r2局部变量(它们可能位于堆栈或CPU寄存器中); r3x共享变量(这些是在主内存中)。如果不考虑这一点,这些例子将没有意义。

答案 1 :(得分:3)

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

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

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

此代码示例的整个概念是证明需要正确同步。

答案 2 :(得分:1)

javac没有在很大程度上优化代码。 JIT优化了代码,但对重新排序代码相当保守。 CPU可以重新排序执行,它可以在很小程度上完成分配。

强制CPU不进行指令级优化是相当昂贵的,例如它可以减慢10倍或更多。 AFAIK,Java设计人员希望指定在大多数CPU上有效工作所需的最小保证。