9

JLS 中,第 17.4.5 节。Happens-before Order,它说

当且仅当所有顺序一致的执行都没有数据竞争时,程序才能正确同步。

根据是否正确同步的程序仍然允许数据竞争?(第一部分)中的讨论,我们得到以下结论:

程序可以正确同步并具有数据竞争。

两个结论的结合意味着它必须存在这样一个例子:

程序的所有顺序一致执行都是无数据竞争的,但此类程序的正常执行(除顺序一致执行之外的执行)包含数据竞争。

经过深思熟虑,我仍然找不到这样的代码示例。那你呢?

4

1 回答 1

8

“一个程序可以正确同步并有数据竞争”是不正确的。assylias 在该讨论中的示例未正确同步。从更高级别的功能角度来看,它是正确的——它包含的数据竞争并不表现为错误。这是一场所谓的“良性”数据竞赛,但在讨论 JLS 定义时这无关紧要。

保证顺序一致执行不包含数据竞争的程序在任何执行中都不包含数据竞争,无论是否顺序一致。正如 JLS 所说,

这对程序员来说是一个极其有力的保证。程序员不需要推理重新排序来确定他们的代码包含数据竞争。因此,在确定他们的代码是否正确同步时,他们不需要考虑重新排序。一旦确定代码正确同步,程序员就不必担心重新排序会影响他或她的代码。

所以请注意,出于对程序员的礼貌,正确同步程序的定义被缩小为仅顺序一致的执行,这给了他强有力的保证,即顺序一致的执行是他或她唯一需要推理的执行以及所有其他执行。将自动获得相同的保证。

更新

JMM 使用的术语很容易迷失方向,而细微的误解会导致后来的深刻误解。因此,请牢记这些:

  • 执行只是一组线程间操作。它没有先验顺序。特别是,它没有时间顺序

这是一个违反直觉的定义,所以我们必须小心:每次我们说执行时,我们必须确保想象一袋动作,而不是一串动作。每当我们定义一个偏序时,我们应该想象有几个袋子排成一列

  • 程序包含执行动作的指令。每个这样的指令可以执行零次或多次,为特定执行贡献零次或多次不同的动作;
  • 执行可能有也可能没有执行顺序,这是所有动作的总顺序;
  • 如果所有共享变量都是易失的,那么您将获得顺序一致的执行。这种执行总是有一定的执行顺序;
  • 顺序不一致的执行是您对程序的实际执行:涉及非易失性变量,编译器重新排序读取和写入,有缓存,线程本地存储等。
  • 同步顺序是执行完成的所有同步操作的总顺序。就执行本身而言,它仍然是部分顺序,因为并非所有动作都是同步动作;最值得注意的是,非易失性变量的读取和写入。每次执行,无论是否顺序一致,都有明确的同步顺序;
  • 同样,happens-before顺序是为程序的特定执行定义的,并且是作为与程序顺序的同步顺序的传递闭包派生的。

有趣的是,如果您的所有共享变量都是 volatile 的,那么同步顺序将变为总顺序,因此符合执行顺序的定义。通过这种方式,我们从不同的角度得出结论,这样的程序的所有执行都将是顺序一致的。

我已经深入挖掘了数据竞赛定义中 JLS 错误的根源:

“当一个程序包含两个冲突的访问(第 17.4.1 节),这些访问没有按照发生前的关系排序时,就可以说它包含数据竞争。”

首先,不是程序包含数据竞争,而是程序执行。如果我们回顾一下定义 Java 内存模型的原始论文,我们会看到这一点得到纠正:

“如果两个访问xy来自不同的线程,它们会在程序执行中形成数据竞争,它们会发生冲突,并且它们不是按发生前排序的。”

然而,这仍然给我们留下了对被定义为数据竞争的 volatile vars 的操作。考虑以下发生前图:

Thread W        w1 ----> w2
                |
                 \
Thread R     r0 ----> r1

r1观察到写入w1。它之前是另一个读取r0,写入之后是另一个w2现在请注意,在r0w1w2之间没有路径;同样在r1w2之间。所有这些都是定义上的数据竞争的例子。

然而,更深入地挖掘,我在 memoryModel 邮件列表中找到了这篇文章。它说“数据竞争应该被定义为对非易失性变量的冲突操作,这些变量不是按发生前排序的”。只有这样添加才能关闭漏洞,但这仍然没有进入官方 JLS 版本。

于 2012-08-19T06:32:03.030 回答