Java 内存模型:volatile 变量和 happens-before
Java memory model: volatile variables and happens-before
我想阐明 happens-before 关系如何与 volatile 变量一起使用。让我们有以下变量:
public static int i, iDst, vDst;
public static volatile int v;
和线程 A:
i = 1;
v = 2;
和线程 B:
vDst = v;
iDst = i;
根据 Java 内存模型 (JMM),下列陈述是否正确?如果不正确,正确的解释是什么?
i = 1
总是发生在之前v = 2
v = 2
先于 vDst = v
在 JMM 中仅当它实际发生在时间之前
如果 v = 2
实际上发生在 vDst = v
之前
- 否则
i = 1
和 iDst = i
之间的顺序未定义,iDst
的结果值也未定义
逻辑错误:
JMM中没有"wall clock time"概念,我们应该依赖同步顺序作为v = 2
和vDst = v
的排序指南.有关详细信息,请参阅所选答案。
是的,所有这些都是正确的 根据 this section 关于 happens-before 顺序:
i = 1
总是发生在之前v = 2
因为:
If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).
v = 2
happens-before vDst = v
在 JMM 中只有当它实际发生在时间之前时,因为 v
是易变的,并且
A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.
i = 1
先于 iDst = i
在 JMM 中(并且 iDst
将被预测分配 1)如果 v = 2
实际上及时发生在 vDst = v
之前。这是因为在这种情况下:
i = 1
先于 v = 2
v = 2
先于 vDst = v
vDst = v
先于 iDst = i
If hb(x, y) and hb(y, z), then hb(x, z).
编辑:
正如@user2357112 所说,陈述 2 和 3 似乎并不准确。 happens-before 关系不一定在具有这种关系的动作之间强加时间顺序,如 JLS 的同一部分所述:
It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.
因此,根据JLS中提到的规则,我们不应该对语句的实际执行时间做出假设。
i = 1
总是发生在之前v = 2
没错。按 JLS 部分 17.4.5、
If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).
v = 2
先于 vDst = v
在 JMM 中仅当它实际发生在时间之前
如果 v = 2
实际上发生在时间 vDst = v
之前
错误。 happens-before 顺序并不能保证在物理时间内发生的事情先于彼此发生。来自 JLS 的同一部分,
It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.
但是,保证 v = 2
先发生 vDst = v
和 i = 1
先发生 iDst = i
如果 v = 2
在同步顺序中出现在 vDst = v
之前,则执行的同步操作的总顺序经常被误认为是实时顺序。
- 否则
i = 1
和 iDst = i
之间的顺序未定义,iDst
的结果值也未定义
如果在同步顺序中 vDst = v
在 v = 2
之前,但实际时间不在其中,就会出现这种情况。
所有个同步动作(volatilew/r、lock/unlock等)组成一个总序。 [1] 这是一个非常有力的声明;它使分析更容易。对于你的 volatile v
,要么读在写之前,要么写在读之前,在这个总顺序中。顺序当然要看实际执行了。
根据总顺序,我们可以建立部分顺序happens-before。 [2] 如果对变量(volatile 或非 volatile)的所有读取和写入都在偏序链上,则很容易分析 - 读取会看到紧接在前的写入。这是 JMM 的要点 - 在 read/writes 上建立订单,这样它们就可以像顺序执行一样推理。
但是如果易失性读取在易失性写入之前怎么办?我们在这里需要另一个关键约束——读不能看到写。 [3]
因此,我们可以推断,
- 读取
v
看到 0(初始值)或 2(易失性写入)
- 如果看到2,一定是读在写之后;在这种情况下,我们有
happens-before
链。
最后一点 - 读取 i
必须看到对 i
的写入之一;在这个例子中,0 或 1。它永远不会看到不是来自任何写入的魔法值。
引用 java8 规范:
[1] http://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.4
[2]http://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.5
[3]http://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.7
总订单的乱想:
由于这个总顺序,我们可以说一个同步动作发生在另一个之前,就好像及时一样。那个时间可能与挂钟不一致,但它对我们的理解来说是一个不错的心智模型。 (实际上,java中的一个动作对应了一场硬件活动风暴,无法为其定义一个时间点)
甚至物理时间也不是绝对的。请记住,光在 1ns 内传播 30cm;在今天的 CPU 上,时间顺序绝对是相对的。总顺序实际上要求一个动作与下一个动作之间存在因果关系。这是一个非常强烈的要求,您敢打赌 JVM 会努力优化它。
我想阐明 happens-before 关系如何与 volatile 变量一起使用。让我们有以下变量:
public static int i, iDst, vDst;
public static volatile int v;
和线程 A:
i = 1;
v = 2;
和线程 B:
vDst = v;
iDst = i;
根据 Java 内存模型 (JMM),下列陈述是否正确?如果不正确,正确的解释是什么?
i = 1
总是发生在之前v = 2
v = 2
先于vDst = v
在 JMM 中仅当它实际发生在时间之前
如果 - 否则
i = 1
和iDst = i
之间的顺序未定义,iDst
的结果值也未定义
v = 2
实际上发生在 vDst = v
之前
逻辑错误:
JMM中没有"wall clock time"概念,我们应该依赖同步顺序作为v = 2
和vDst = v
的排序指南.有关详细信息,请参阅所选答案。
是的,所有这些都是正确的 根据 this section 关于 happens-before 顺序:
i = 1
总是发生在之前v = 2
因为:
If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).
v = 2
happens-beforevDst = v
在 JMM 中只有当它实际发生在时间之前时,因为v
是易变的,并且
A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.
i = 1
先于iDst = i
在 JMM 中(并且iDst
将被预测分配 1)如果v = 2
实际上及时发生在vDst = v
之前。这是因为在这种情况下:i = 1
先于v = 2
v = 2
先于vDst = v
vDst = v
先于iDst = i
If hb(x, y) and hb(y, z), then hb(x, z).
编辑:
正如@user2357112 所说,陈述 2 和 3 似乎并不准确。 happens-before 关系不一定在具有这种关系的动作之间强加时间顺序,如 JLS 的同一部分所述:
It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.
因此,根据JLS中提到的规则,我们不应该对语句的实际执行时间做出假设。
i = 1
总是发生在之前v = 2
没错。按 JLS 部分 17.4.5、
If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).
v = 2
先于vDst = v
在 JMM 中仅当它实际发生在时间之前
如果
v = 2
实际上发生在时间 vDst = v
之前
错误。 happens-before 顺序并不能保证在物理时间内发生的事情先于彼此发生。来自 JLS 的同一部分,
It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.
但是,保证 v = 2
先发生 vDst = v
和 i = 1
先发生 iDst = i
如果 v = 2
在同步顺序中出现在 vDst = v
之前,则执行的同步操作的总顺序经常被误认为是实时顺序。
- 否则
i = 1
和iDst = i
之间的顺序未定义,iDst
的结果值也未定义
如果在同步顺序中 vDst = v
在 v = 2
之前,但实际时间不在其中,就会出现这种情况。
所有个同步动作(volatilew/r、lock/unlock等)组成一个总序。 [1] 这是一个非常有力的声明;它使分析更容易。对于你的 volatile v
,要么读在写之前,要么写在读之前,在这个总顺序中。顺序当然要看实际执行了。
根据总顺序,我们可以建立部分顺序happens-before。 [2] 如果对变量(volatile 或非 volatile)的所有读取和写入都在偏序链上,则很容易分析 - 读取会看到紧接在前的写入。这是 JMM 的要点 - 在 read/writes 上建立订单,这样它们就可以像顺序执行一样推理。
但是如果易失性读取在易失性写入之前怎么办?我们在这里需要另一个关键约束——读不能看到写。 [3]
因此,我们可以推断,
- 读取
v
看到 0(初始值)或 2(易失性写入) - 如果看到2,一定是读在写之后;在这种情况下,我们有
happens-before
链。
最后一点 - 读取 i
必须看到对 i
的写入之一;在这个例子中,0 或 1。它永远不会看到不是来自任何写入的魔法值。
引用 java8 规范:
[1] http://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.4
[2]http://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.5
[3]http://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.7
总订单的乱想:
由于这个总顺序,我们可以说一个同步动作发生在另一个之前,就好像及时一样。那个时间可能与挂钟不一致,但它对我们的理解来说是一个不错的心智模型。 (实际上,java中的一个动作对应了一场硬件活动风暴,无法为其定义一个时间点)
甚至物理时间也不是绝对的。请记住,光在 1ns 内传播 30cm;在今天的 CPU 上,时间顺序绝对是相对的。总顺序实际上要求一个动作与下一个动作之间存在因果关系。这是一个非常强烈的要求,您敢打赌 JVM 会努力优化它。