发生在易失性之前和重新排序

时间:2019-01-07 20:22:59

标签: java multithreading java-memory-model jls

有多个代码示例,这些示例假定以下指令(1)(2)无法重新排序:

int value;
volatile boolean ready;

// ...

value = 1;     // (1)
ready = true;  // (2)

后面的堆栈溢出答案是指JLS §17.4.5

  

如果x和y是同一线程的动作,并且x按程序顺序位于y之前,则hb(x,y)。

但是我不明白为什么这应该在这里应用,因为JLS Example 17.4-1还指出:

  

[...]编译器可以在不影响单独执行该线程的情况下,对每个线程中的指令重新排序。

这很明显。

JLS中volatile的所有其他定义仅针对同一个volatile变量,而不针对其他动作:

  

在随后每次对该字段进行读取之前,都会对易失字段(第8.3.1.4节)进行写操作。


这使我感到困惑,因为人们看到不能对volatile(读或写)的使用进行重新排序的保证。

能否请您根据JLS或基于JLS的其他来源进行解释。

1 个答案:

答案 0 :(得分:1)

孤立地,您的代码不能保证任何事情。这里涉及第二个线程,我们也需要它的代码!您链接的教程同时显示两个线程是有原因的。

如果两个线程的代码是这样的:

int value;
volatile boolean ready;

// Thread - 1
value = 1;     // (1)
ready = true;  // (2)

// Thread - 2
if (ready) {  // (3)
    x = value // (4)
}

然后,由于程序顺序,我们在(1)和(2)之间具有先发生后关系:

  

如果x和y是同一线程的动作,并且x按程序顺序位于y之前,则hb(x,y)。

由于ready易变,我们在(2)和(3)之间具有先发生后关系:

  

在随后的每个后续操作之前,都会对volatile字段(第8.3.1.4节)进行写操作   阅读该字段。

又由于程序顺序,我们在(3)和(4)之间具有先发生后关系:

  

如果x和y是同一线程的动作,并且x按程序顺序位于y之前,则hb(x,y)。

所以在链(1)→(2),(2)→(3),(3)→(4)之前发生了

并且因为before-before是传递关系(如果A发生在B之前,B发生在C之前,那么A发生在C之前),这意味着(1)发生在(4)之前。

如果我们翻转(3)和(4),以便第二个线程在读取value之前先读取ready,则发生在链断裂之前的情况发生了,我们不再对读取有任何保证来自value

这是一个不错的tutorial,还有更多的JMM陷阱,包括这个陷阱。

Java内存模型不是很有趣吗?