繁体   English   中英

Java内存模型:volatile变量和之前发生的

[英]Java memory model: volatile variables and 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 发生在JMM中的vDst = v 之前 ,只有它实际发生在时间之前
  • i = 1 发生 -在JMM中iDst = i 之前 (并且iDst将被预测为1 )如果v = 2实际发生在vDst = v之前
  • 否则顺序之间i = 1iDst = i是未定义和的结果值iDst未定义以及

错误的逻辑:

JMM中没有“挂钟时间”概念,我们应该依赖同步顺序作为v = 2vDst = v的排序指南。 有关详细信息,请参阅所选答案。

  • i = 1总是 v = 2 之前发生

真正。 按JLS第17.4.5节

如果xy是同一个线程的动作,并且x在程序顺序中出现在y之前,那么hb(x,y)


  • v = 2 发生在JMM中的vDst = v 之前 ,只有它实际发生在时间之前
  • i = 1 发生 -在JMM中iDst = i 之前 (并且iDst将被预测为1 )如果v = 2实际发生在vDst = v之前

假。 事先发生的命令并不能保证物理时间内彼此之前发生的事情。 从JLS的同一部分,

应该注意的是,两个动作之间存在的先发生关系并不一定意味着它们必须在实现中以该顺序发生。 如果重新排序产生的结果与合法执行一致,则不是非法的。

但是,保证v = 2 发生 - 在 vDst = v 之前发生并且i = 1 发生 - 在 iDst = i 之前如果v = 2在同步顺序中在vDst = v之前vDst = v ,则执行同步动作的总顺序这通常被误认为是实时订单。


  • 否则顺序之间i = 1iDst = i是未定义和的结果值iDst未定义以及

如果vDst = v在同步顺序中位于v = 2之前,则会vDst = v这种情况,但实际时间不会进入。

是的,所有这些都是正确的 根据本节关于发生 - 在订单之前:

  1. i = 1总是 v = 2 之前发生,因为:

如果x和y是同一个线程的动作,并且x在程序顺序中出现在y之前,那么hb(x,y)

  1. v = 2 发生在JMM中的vDst = v 之前 ,只有当它实际发生在时间之前,因为v是易失性的,并且

写入易失性字段(第8.3.1.4节) - 在每次后续读取该字段之前发生。

  1. 如果v = 2实际发生在vDst = v之前,则i = 1 发生在JMM中iDst = i 之前 (并且iDst将被预测地指定为1)。 这是因为在这种情况下:
    • i = 1 发生在 v = 2 之前
    • v = 2 发生在 vDst = v 之前
    • vDst = v 发生在 iDst = i 之前

如果是hb(x,y)hb(y,z) ,那么hb(x,z)

编辑:

正如@ user2357112所论证的那样,似乎语句2和3并不准确。 发生在之前的关系并不一定在具有这种关系的动作之间强加一个时间顺序,如JLS的同一部分所述:

应该注意的是,两个动作之间存在的先发生关系并不一定意味着它们必须在实现中以该顺序发生。 如果重新排序产生的结果与合法执行一致,则不是非法的。

因此,就JLS中提到的规则而言,我们不应该对执行报表的实际时间做出假设。

所有同步操作(易失性w / r,锁定/解锁等)形成总订单。 [1]这是一个非常强烈的声明; 它使分析更容易。 对于易失性v ,要么在写入之前读取,要么在读取之前读取,在此总顺序中。 订单取决于当然的实际执行情况。

从总订单中,我们可以建立之前发生的部分订单。 [2]如果对变量(易失性或非易失性)的所有读写都在部分订单链上,则很容易分析 - 读取会看到前一次写入。 这是JMM的要点 - 在读/写上建立订单,因此它们可以像顺序执行一样被推理。

但是如果易失性读取在易失性写入之前怎么办? 我们需要另一个关键约束 - 读取不能看到写入。 [3]

因此,我们可以推断,

  1. 读取v看到0(初始值)或2(易失写入)
  2. 如果它看到2,则必须是在写入之后读取的情况; 在那种情况下,我们happens-beforehappens-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内传播30厘米; 在今天的CPU上,时间顺序绝对是相对的。 总订单实际上要求从一个动作到下一个动作存在因果关系。 这是一个非常强烈的要求,你敢打赌JVM会努力优化它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM