当前位置: 首页 > 知识库问答 >
问题:

Java内存模型:易失性变量和发生-之前

孔安阳
2023-03-14

我想澄清发生前关系是如何与不稳定变量一起工作的。让我们有以下变量:

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的订购指南。有关更多详细信息,请参阅所选答案。


共有3个答案

束新
2023-03-14

所有同步操作(易失性w/r、锁定/解锁等)形成一个总顺序。[1]这是一个非常有力的陈述;它使分析更容易。对于易失性v,要么读取在写入之前,要么写入在读取之前,按此总顺序排列。当然,顺序取决于实际执行。

根据总的顺序,我们可以建立之前发生的部分顺序。[2] 如果一个变量的所有读写操作(无论是否易变)都在一个偏序链上,那么很容易进行分析——读操作可以看到前面的写操作。这就是JMM的要点——在读/写操作上建立命令,这样就可以像顺序执行一样对它们进行推理。

但是,如果易失性读取在易失性写入之前呢?这里我们需要另一个关键的约束——读不能看到写。[三]

因此,我们可以推理,

  1. 读取v会看到0(初始值)或2(易失性写入)
  2. 如果看到2,则必须是读在写之后;在这种情况下,发生在链之前

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厘米;在今天的处理器上,时间顺序肯定是相对的。总顺序实际上要求从一个动作到下一个动作有因果关系。这是一个非常强的需求,你可以打赌JVM会努力优化它。

汤玉宸
2023-03-14
  1. i=1总是发生在 v=2之前,因为:

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

对易失性字段(§8.3.1.4)的写入发生在该字段的每次后续读取之前。

  • 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中提到的规则,我们不应该对报表的实际执行时间进行假设。

鱼锦
2023-03-14
  • i=1总是发生在v=2

真实。根据JLS第17.4.5节,

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

  • v=2发生在JMM中的vDst=v之前,前提是它实际上发生在时间之前

错误的“先发制人”命令不能保证在物理时间内事情先发制人。来自JLS的同一部分,

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

然而,如果v=2出现在vDst=v之前,并且i=1出现在iDst=i之前,则可以保证v=2出现在vDst=之前v在同步顺序中,执行的同步操作的总顺序,经常被误认为实时顺序。

  • 否则,i=1iDst=i之间的顺序未定义,iDst的结果值也未定义

如果同步顺序中vDst=v出现在v=2之前,但实际时间没有出现,则会出现这种情况。

 类似资料:
  • 问题内容: 我想说明 先发生的 关系如何与 volatile 变量一起使用。让我们有以下变量: 和线程A: 和线程B: 根据Java内存模型(JMM),以下语句正确吗? 如果没有,正确的解释是什么? 总是在 发生之前 __仅在JMM中实际发生时才 发生- 在JMM中 __ 如果实际上发生在时间之前,则发生在JMM中-before -before (并且将可预测地分配) 否则,和之间的顺序不确定,并

  • 我对易变语义几乎没有疑问 假设有三个线程T1、T2和T3,以及给定类的一个实例。 假设发生以下读/写操作序列: 我知道可以保证点9的T1将看到点7的值,点10的T1将看到点6的值(确切地说,至少和这个值一样最新)。 但是,这些说法是真的吗? Java内存模型保证,点11处的T1将看到至少与点5处的T3相同的最新值(来自T3或更实际的本地内存的值,但即使共享内存中有更实际的值,T1也可能看不到) 请

  • 问题内容: 我尝试了解为什么此示例是正确同步的程序: 由于存在冲突的访问(存在对a的写入和读取),因此在每个顺序一致性中,必须在访问之间的关系之前执行。假设顺序执行之一: 是1发生-在2之前发生,为什么? 问题答案: 不,在相同变量的易失性写入之前(以同步顺序),在易失性写入 之前 不一定 会发生 易失性读取。 这意味着它们可能处于“数据争用”中,因为它们“冲突的访问未按先发生后关系进行排序”。如

  • 官方记录说 写入易失性字段与监视器释放具有相同的记忆效果,从易失性字段读取与监视器获取具有相同的记忆效果。 和 有效地,挥发性的语义学得到了实质性的加强,几乎达到了同步的水平。为了可见性的目的,挥发性字段的每次读取或写入都像“半”次同步。 从这里开始。 这是否意味着,对volatile变量的任何写入都会使执行线程将其缓存刷新到主存中,而每次从volatile字段读取都会使线程从主存重新读取其变量?

  • 问题内容: 我有一段代码看起来像这样: 片段A: 根据我的理解,由于的读取不同步,因此如果线程A 在下午1点创建了一个,而线程B 在下午2点进行了读取,则很可能返回0或1(即使线程A在1.05 pm完成了对对象的初始化) )。 所以我添加到: 片段B: 一切都很好,除了我在想,如果我将其修改为 Snippet C ,变量是否仍正确同步? 片段C: 使用 代码片段C ,是否可以保证线程A在下午1:0

  • 在《有效Java》一书中: 后台线程不会在一秒钟后停止。因为提升,在JVM中优化,HotSpot服务器VM做。 您可以在以下主题中查看这一点: 为什么HotSpot会使用提升优化以下内容?。 优化过程如下: 有两种方法可以解决这个问题。 volatile的函数是 -禁止提升 -它保证任何读取该字段的线程都会看到最近写入的值 上面的代码在有效Java书中是正确的,它相当于使用来装饰。 如果此方法忽略