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

发生在Java中与易失性字段和同步块的关系之前,以及它们对非易失性变量的影响?

越飞语
2023-03-14

我对线程的概念还是相当陌生的,并试图对它有更多的了解。最近,我看到Jeremy Manson写的一篇关于Volatile在Java中的含义的博文,他写道:

当一个线程写入一个易失性变量,而另一个线程看到该写操作时,第一个线程将告诉第二个线程内存中的所有内容,直到它执行了对该易失性变量的写操作为止。[...] 线程1在写入[volatile]ready之前看到的所有内存内容,在读取ready的值true之后,必须对线程2可见。[我自己加的重点]

现在,这是否意味着线程1在写入易失性变量时存储在其内存中的所有变量(易失性或非易失性)将在线程2读取该易失性变量后对其可见?如果是这样,是否可以从官方Java文档/Oracle源代码中将该语句拼凑在一起?从Java的哪个版本开始,这将起作用?

特别是,如果所有线程共享以下类变量:

private String s = "running";
private volatile boolean b = false;

线程1首先执行以下操作:

s = "done";
b = true;

线程2随后执行(在线程1写入volatile字段之后):

boolean flag = b; //read from volatile
System.out.println(s);

是否保证打印“完成”?

如果不是将b声明为volatile而是将写入和读取放入同步的块,会发生什么情况?

此外,在题为“线程之间是否共享静态变量”的讨论中@树写:

不要使用volatile来保护多个共享状态。

为什么?(对不起,我还不能对其他问题发表评论,否则我会在那里提问……)


共有3个答案

龙毅
2023-03-14

是否保证打印“完成”?

正如Java并发实践中所说:

当线程A写入volatile变量,然后线程B读取同一变量时,在写入volatile变量之前对A可见的所有变量的值在读取volatile变量后对B可见。

所以是的,这保证打印"完成"。

如果不是将b声明为volatile,而是将写入和读取放入同步块,会发生什么?

这也将保证同样的结果

不要使用volatile来保护多个共享状态。

为什么?

因为,不稳定只保证可见性。它不能保证原子性。如果我们在一个被一个线程访问的方法中有两次易失性写入,而另一个线程正在访问这些易失性变量,那么当线程执行该方法时,可能线程A将在操作中间被线程B抢占(例如在第一次易失性写入之后,但在线程A进行第二次易失性写入之前)。所以保证操作的原子性同步是最可行的出路。

奚光霁
2023-03-14

如果不是将b声明为volatile,而是将写入和读取放入同步块,会发生什么?

如果且仅当您使用相同的锁保护所有此类同步块时,您将获得与volatile示例相同的可见性保证。此外,您还可以对此类同步块的执行进行互斥。

不要使用volatile来保护多个共享状态。

为什么?

volatile不保证原子性:在您的示例中,s变量也可能在显示写入后被其他线程变异;读取线程不能保证它看到了哪个值。在读取volatile之后,但在读取s之前发生的对s的写入也是如此。

什么是安全的做,并在实践中完成,共享不可变的状态,可传递地访问从写入到易失性变量的引用。所以也许这就是“一片共享状态”的意思。

可以从官方Java文档/Oracle源代码中将该语句拼凑在一起吗?

规范中的引用:

对易失性变量v的写入(§8.3.1.4)与任何线程对v的所有后续读取同步(其中“后续”是根据同步顺序定义的)。

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

如果动作x与后续动作y同步,那么我们还有hb(x,y)。

这应该足够了。

从哪个版本的Java开始,这将起作用?

Java语言规范第3版介绍了对内存模型规范的重写,这是实现上述保证的关键。注意:大多数以前的版本都好像有保证一样,许多代码行实际上都依赖于它。当人们发现担保事实上并不存在时,他们感到惊讶。

文喜
2023-03-14

是的,可以保证线程2将打印“完成”。当然,这是如果线程1中对b的写入实际上发生在线程2中从b读取之前,而不是同时或更早发生!

这里推理的核心是“先发生后发生”的关系。多线程html" target="_blank">程序执行被视为由事件组成。事件可以通过“先发生后关系”进行关联,即一个事件先于另一个事件发生。即使两个事件没有直接的关联,如果你能从一个事件追溯到另一个事件,那么你可以说一个发生在另一个之前。

在您的情况下,您有以下事件:

  • 线程1写入s
  • 线程1写入b
  • 线程2读取b
  • 线程2从s

以下规则开始发挥作用:

  • "如果x和y是同一个线程的动作,并且在程序顺序中x在y之前,那么hb(x,y)."(程序顺序规则)
  • 对易失性字段的写入(§8.3.1.4)发生在该字段的每次后续读取之前。(易失性规则)

因此,在关系存在之前,会发生以下情况:

  • 线程1写入s发生在线程1写入b(程序顺序规则)
  • 线程1写入b发生在线程2读取b(易失性规则)
  • 线程2从b读取发生在线程2从s读取之前(程序顺序规则)

如果你沿着这条链,你可以看到结果:

  • 线程1写入s发生在线程2读取s
 类似资料:
  • 我想澄清发生前关系是如何与不稳定变量一起工作的。让我们有以下变量: 和线程A: 和线程B: 根据Java内存模型(JMM),以下语句是否正确?如果不是,正确的解释是什么? 总是发生-之前 在JMM中发生在之前,只有当它实际发生在时间之前 在JMM中发生在之前(并且将被可预测地分配)如果实际发生在

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

  • 在阅读了这个问题和这个(尤其是第二个答案)之后,我对volatile及其关于记忆障碍的语义感到非常困惑。 在上面的例子中,我们写入一个易失性变量,这会导致一个mitch,这反过来会将所有挂起的存储缓冲区/加载缓冲区刷新到主缓存,使其他缓存行无效。 然而,非易失性字段可以优化并存储在寄存器中,例如?那么,我们如何才能确保给定一个写入易失性变量之前的所有状态变化都是可见的呢?如果我们有1000件东西呢

  • 它几乎是线程安全的单例的正确实现。我看到的唯一问题是: 初始化字段的可以在完全初始化之前发布。现在,第二个线程可以在不一致的状态下读取。 但是,在我看来,这只是问题。这是唯一的问题吗?(并且我们可以使不稳定)。

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

  • 谢谢,伊利亚