【问题标题】:Why is this behavior allowed in the Java Memory Model?为什么 Java 内存模型允许这种行为?
【发布时间】:2012-10-27 14:56:58
【问题描述】:

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 的唯一值是 0 和 42。据此,编译器可以推断出,此时 我们执行r1 = x 的地方,要么我们刚刚执行了 42 的写入到 x,或者我们刚刚阅读了 x 并看到了值 42。无论哪种情况,它 读取x 以查看值是合法的 42.然后可以将r1 = x更改为r1 = 42;这将允许 y = r1 转换为 y = 42 并更早执行,从而导致 有问题的行为。在这种情况下,写入y 已提交 首先。

我的问题是,究竟是什么样的编译器优化? (我对编译器一窍不通。)既然42是有条件写的,那么当if语句满足时,编译器怎么会决定去写x呢?

其次,即使编译器进行了这种推测性优化,并提交了y = 42 和 then finally make r3 = 42 ,是不是违反了因果循环,因为现在已经没有因果区别了?

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

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

【问题讨论】:

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


    【解决方案1】:

    如上所述,曾经写入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 即可知道要向 y 写入什么。

    关于符号的一般说明:r1r2r3局部变量(它们可以在堆栈上或 CPU 寄存器中); xy共享变量(它们在主内存中)。如果不考虑这一点,这些示例将毫无意义。

    【讨论】:

      【解决方案2】:

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

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

      对于单线程执行,这段代码等同于原始代码,因此是合法的。那么,如果 Thread2 的代码在 step 1 和 step2 之间执行(这很有可能),那么 r3 也被赋值为 42。

      此代码示例的整个想法是演示正确同步的需要。

      【讨论】:

      • @Alexei 这解释了一些。但是,编译器不应该使用 r3 = 0 而不是 r3 = 42 吗?或者他们只是在展示“一种可能性”!
      • 编译器不会生成r3=42,它只会让r3=x 保持原样。编译器优化并不总是执行到最大深度。如果优化违反正确性的可能性很小,则放弃它。在给定的代码中,没有这种情况,但如果存在其他代码,它们就会出现。此外,编译器可以确定r3=0r3=x 的价格相同。
      【解决方案3】:

      javac 没有在很大程度上优化代码是毫无价值的。 JIT 优化了代码,但对重新排序代码相当保守。 CPU 可以重新排序执行,并且在很小的程度上执行此操作。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-05-29
        • 2011-08-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-31
        • 1970-01-01
        相关资源
        最近更新 更多