有多个代码示例,这些示例假定以下指令(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的其他来源进行解释。
答案 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内存模型不是很有趣吗?