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中是合法的?
答案 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写入y
。 r3
变量实际上是多余的,可以从机器代码中完全消除。因此,示例中的代码仅显示从x
到y
的因果箭头的外观,但详细分析表明实际上没有因果关系。令人惊讶的结果是,y
的写入可以提前提交。
关于优化的一般说明:我认为你熟悉从主存储器中读取所涉及的性能惩罚。这就是为什么JIT编译器倾向于拒绝尽可能地执行它,并且在这个示例中,事实证明它实际上不需要读取x
以便知道写入{{1}的内容}。
关于符号的一般说明:y
,r1
,r2
是局部变量(它们可能位于堆栈或CPU寄存器中); r3
,x
是共享变量(这些是在主内存中)。如果不考虑这一点,这些例子将没有意义。
答案 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上有效工作所需的最小保证。