Java 易失性读刷新写入和易失性写更新读取

2022-09-03 05:44:15

我了解读-获取(不与之后的后续读/写操作重新排序)和写-释放(不与之前的读/写操作重新排序)。我的 q 是:-

  1. 在读取获取的情况下,它前面的写入操作是否会被刷新?
  2. 在写入释放的情况下,以前的读取是否会更新?

另外,读-获取是否与易失性读取相同,写入释放是否与 Java 中的易失性写入相同?

为什么这很重要,让我们以写入发布为例。

y = x; // a read.. let's say x is 1 at this point
System.out.println(y);// 1 printed
//or you can also consider System.out.println(x);
write_release_barrier();
//somewhere here, some thread sets x = 2
ready = true;// this is volatile
System.out.println(y);// or maybe, println(x).. what will be printed?

此时,x 是 2 还是 1?在这里,考虑准备好不稳定。我明白,在波动之前的所有商店将首先变得可见。然后只有挥发性才会变得可见。谢谢。

编号:- http://preshing.com/20120913/acquire-and-release-semantics/


答案 1

否:并非所有写入操作都会刷新,也不会更新所有读取。

Java在多线程的“发生之前”的基础上工作。基本上,如果A发生在B之前,B发生在C之前,那么A发生在C之前。所以你的问题相当于是否正式发生 - 在读取x的某个操作之前。x=2

边发生之前基本上是通过同步关系建立的,这些关系在 JLS 17.4.4 中定义。有几种不同的方法可以做到这一点,但对于易失性,它基本上相当于写入易失性发生 - 在读取相同的易失性之前:

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

鉴于此,如果你的线程写入 ,那么仅写入并不意味着在它之前发生任何事情(就写入而言)。事实恰恰相反。写到发生在其他线程上的事情之前,如果他们读.ready = truereadyready

因此,如果另一个线程(设置)在设置已写入就绪,并且此线程(您在上面发布)然后读取 ,那么它将看到 。这是因为写入发生在读取之前,因此读取线程可以看到写入线程所做的一切(直到并包括写入)。否则,你有一场数据竞赛,基本上所有的赌注都关闭了。x = 2x = 2readyx = 2

一些额外的注意事项:

  • 如果您没有发生前优势,您可能仍会看到更新;只是你不能保证。所以,不要以为如果你不读一个写到,那么你仍然会看到x=1。您可能会看到 x=1 或 x=2,或者可能看到其他一些写入(最高(包括默认值 x=0)ready
  • 在您的示例中,始终为 1,因为您不会在“此处的某个地方”注释之后重新阅读。出于这个答案的目的,我假设在 前面或之后有第二行。如果没有,那么y的值将与第一个中的值相同(假设没有其他线程直接更改它 - 如果它是局部变量,则可以保证这一点),因为线程中的操作总是显示为未重新排序。yxy=xready = trueprintln

答案 2

Java 内存模型不是在“读取-获取”和“写入-释放”方面指定的。这些术语/概念来自其他上下文,正如您引用的文章非常清楚的那样,它们经常(由不同的专家)用于表示不同的东西。

如果你想了解易失性在Java中是如何工作的,你需要了解Java内存模型和Java术语......这是(幸运的是)有充分根据且精确指定的1。尝试将 Java 内存模型映射到“读取-获取”和“写入-释放”语义是一个坏主意,因为:

  • “读取-获取”和“写入-发布”术语和语义没有很好地指定,并且

  • 假设的JMM->“读-获取”/“写-释放”语义映射只是JMM的一种可能的实现。其他映射可能以不同且同样有效的语义存在


1 - ...专家已经注意到某些版本的JMM中的缺陷。但关键是,已经认真尝试提供理论上合理的规范......作为 Java 语言规范的一部分。