“程序可以正确同步并存在数据竞争”的说法是不正确的。 assylias 在该讨论中的示例没有正确同步。从更高级别的功能角度来看,它是正确的——它包含的数据竞争并不表现为错误。这是一场所谓的“良性”数据竞赛,但在讨论 JLS 定义时这无关紧要。
保证顺序一致执行不包含数据竞争的程序在任何执行中都不包含数据竞争,无论是否顺序一致。正如 JLS 所说,
这对程序员来说是一个极强的保证。程序员不需要推理重新排序来确定他们的代码包含数据竞争。因此,在确定他们的代码是否正确同步时,他们不需要考虑重新排序。一旦确定代码已正确同步,程序员就不必担心重新排序会影响他或她的代码。
所以请注意,正确同步程序的定义被缩小为仅顺序一致的执行作为对程序员的礼貌,这给了他强有力的保证,即顺序一致的执行是他唯一的执行否则她需要推理,所有其他处决将自动获得相同的保证。
更新
JMM 使用的术语很容易迷失方向,细微的误解会导致日后产生深刻的误解。因此,请牢记这些:
-
执行只是一组线程间操作。它没有先验顺序。特别是,它没有时间顺序。
这是一个违反直觉的定义,所以我们必须小心:每次我们说执行,我们必须确保想象一个动作包,而不是一个他们的字符串。每当我们定义部分订单时,我们应该想象几个袋子排成一列。
-
程序包含执行操作的指令。每条此类指令可以执行零次或多次,为特定执行贡献零次或多次不同的操作;
- 一个执行可能有也可能没有有一个执行顺序,它是一个所有动作的总顺序 ;
-
顺序一致执行是如果您的所有共享变量都是易失的,您会得到什么。这种执行总是有一定的执行顺序;
-
顺序不一致执行是程序的实际执行:涉及非易失性变量,编译器重新排序读取和写入,有缓存,线程-本地商店等。
-
同步顺序是执行完成的所有同步操作的总顺序。就执行本身而言,它仍然是部分顺序,因为并非所有动作都是同步动作;最值得注意的是,非易失性变量的读取和写入。每次执行,无论是否顺序一致,都有明确的同步顺序;
- 同样,happens-before 顺序是为程序的特定执行定义的,并作为同步顺序与程序顺序的传递闭包派生而来。
有趣的是,如果您的所有共享变量都是可变的,那么同步顺序将成为一个总顺序,因此符合执行顺序的定义。通过这种方式,我们从不同的角度得出这样的结论:此类程序的所有执行都顺序一致。
我已经深入挖掘了数据竞赛定义中 JLS 错误的根源:
“当一个程序包含两个冲突的访问(第 17.4.1 节),它们不是按 happens-before 关系排序的,则称为包含数据竞争。”
首先,包含数据竞争的不是程序,而是程序执行。如果我们回头参考定义 Java 内存模型的 original paper,我们会看到这一点得到纠正:
“如果两个访问 x 和 y 来自不同的线程,它们会在程序的执行中形成数据竞争,它们会发生冲突,并且它们没有按 发生在之前。”
但是,这仍然给我们留下了对 volatile var 的操作被定义为数据竞争。考虑以下 happens-before 图:
Thread W w1 ----> w2
|
\
Thread R r0 ----> r1
r1 观察了写入 w1。它之前是另一次读取,r0,写入之后是另一次,w2。现在注意到 r0 和 w1 或 w2 之间没有路径; r1 和 w2 之间也是如此。根据定义,所有这些都是数据竞争的示例。
然而,深入挖掘,我发现了this post on the memoryModel mailing list。它说“一个数据
比赛应该被定义为对非易失性变量的冲突操作
不是由happens-before排序的。只有这样添加才能关闭漏洞,但这仍然没有进入官方JLS发布。