【问题标题】:Happens before and program order in Java Memory ModelJava内存模型中的发生之前和程序顺序
【发布时间】:2015-09-10 03:25:08
【问题描述】:

我对程序顺序以及它如何影响 JMM 中的重新排序有一些疑问。

在 Java 内存模型中,程序顺序 (po) 定义为程序中每个线程中操作的总顺序。根据JLS,这会导致发生前 (hb) 边缘:

如果xy 是同一个线程的动作并且x 出现在y 之前 程序顺序,然后是 hb(x, y)(即 xhappens-before y)。

所以对于一个简单的程序P:

  initially, x = y = 0
     T1     |     T2
 -----------|-----------
  1. r1 = x | 3. r2 = y  
  2. y = 1  | 4. x = r2

我认为 po(1, 2)po(3, 4)。因此,hb(1, 2)hb(3, 4).

现在假设我想重新排序其中一些语句,给我 P':

  initially, x = y = 0
     T1     |     T2
 -----------|-----------
  2. y = 1  | 3. r2 = y  
  1. r1 = x | 4. x = r2

根据this paper,我们可以重新排序任何两个相邻的语句(例如 1 和 2),前提是重新排序不会消除任何有效执行中的任何传递前发生边缘。然而,由于 hbpo 定义(部分),而 po 是线程操作的总顺序,在我看来它在不违反 hb 的情况下重新排序任何两个语句是不可能的,因此 P' 不是合法的转换。

我的问题是:

  1. 我对 pohb 的理解是否正确,我是否正确定义了 pohb到上面的程序P
  2. 我对 hb 失败的重新排序有何理解?

【问题讨论】:

    标签: java multithreading concurrency java-memory-model


    【解决方案1】:

    您缺少 JLS 的这一部分:

    应该注意的是,两个动作之间存在发生前的关系并不一定意味着它们必须在实现中以该顺序发生。如果重新排序产生与合法执行一致的结果,则不是非法的。

    在您的情况下,由于 1 和 2 不相关,因此可以翻转它们。现在如果 2 是 y = r1,那么 1 必须在 2 之前发生才能得到正确的结果。


    真正的问题发生在多处理器执行上。如果没有任何先发生边界,T2 可能会观察到 2 在 1 之前发生,而不管执行顺序如何。

    这是因为 CPU 缓存。假设 T1 以任意顺序执行 1 和 2。由于不存在happens-before边界,因此这些动作仍在CPU缓存中,根据其他需要,缓存中包含2结果的部分可能会在缓存中包含1结果的部分之前被刷新。

    如果 T2 在这两个缓存刷新事件之间执行,它将观察到 2 已经发生而 1 没有发生,即 2 在 1 之前发生,据 T2 所知。

    如果不允许这样做,则 T1 必须在 1 和 2 之间建立先发生边界。

    在 Java 中有多种方法可以做到这一点。旧的风格是将 1 和 2 放入单独的 synchronized 块中,因为 synchronized 块的开始和结束是发生前的边界,即块之前的任何动作都发生在块内的动作之前,并且任何块内的动作发生在块之后的动作之前。

    【讨论】:

    • 基于您链接的 JLS 的那一部分,我现在的理解是:我们有一个程序 P,程序顺序为 po 和发生在订单 hb 之前。如果我们想转换成 P',我们并不是试图找到 P' 使得 P' 保留 po i> 和 hb - 我们正在为 P' 找到一个全新的 po'hb'结果与原著一致?也就是说,PP' 的顺序可以完全不同,只要它们表现出相同的行为?
    【解决方案2】:

    我认为一个关键问题在于您的构造P'。这意味着重新排序的工作方式是重新排序是全局的 - 整个程序以单一方式重新排序(在每次执行时),它遵循内存模型。然后你试图推理这个P' 并发现不可能有有趣的重新排序!

    实际发生的情况是,与 hb 关系无关的语句没有特定的全局顺序,因此不同的线程可以在同一执行中看到不同的明显顺序。在您的示例中,一组中的 {1,2} 和 {3,4} 语句之间没有边可以以任何顺序看到另一组中的那些。例如,T2 可能在1 之前观察到2,但随后T3T2 相同(具有自己的私有变量),观察到相反的情况!所以没有单一的重新排序 P' - 每个线程都可以观察自己的重新排序,只要它们符合 JMM 规则。

    【讨论】:

      【解决方案3】:

      你所说的P',其实不是一个不同的程序,而是同一个程序P的执行轨迹。它可能是一个不同的程序,但它会有不同的po ,因此不同的hb

      Happens-before 关系限制语句重新排序的可观察效果,​​而不是执行顺序。动作1发生在2之前,但是它们不观察彼此的结果,所以允许它们被重新排序。 hb 保证您将观察到两个动作是按顺序执行的,但只能从同步的上下文中执行(即从其他动作形成 hb12)。你可能会想到 12 说:让我们交换。没有人在看!

      下面是 JLS 的一个很好的例子,它很好地反映了先发生的想法:

      例如,对线程构造的对象的每个字段的默认值的写入不必在该线程开始之前发生,只要没有读取观察到该事实

      在实践中,几乎不可能在线程开始之前对所有对象的默认值写入进行排序,即使它们与该线程中的每个操作都形成 synchronized-with 边缘。启动线程可能不知道它将在运行时构造什么以及将构造多少个对象。但是一旦你引用了一个对象,你会发现默认值写入已经发生了。尚未构造(或已知构造)的对象的排序默认写入通常无法反映在执行中,但它仍然不违反 happens-before 关系,因为这完全是关于可观察的效果。

      【讨论】:

        猜你喜欢
        • 2018-07-06
        • 2015-07-26
        • 1970-01-01
        • 2011-02-04
        • 1970-01-01
        • 2013-12-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多