【问题标题】:Volatile read and non-volatile fields易失性读取和非易失性字段
【发布时间】:2018-01-18 00:27:51
【问题描述】:

在阅读 this questionthis(尤其是第二个答案)后,我对 volatile 及其与内存屏障相关的语义感到非常困惑。

在上面的例子中,我们写入了一个 volatile 变量,这会导致一个 mfence,进而将所有挂起的存储缓冲区/加载缓冲区刷新到主缓存,从而使其他缓存行无效。

但是,可以优化非易失性字段并将其存储在例如寄存器中吗?那么,我们如何确保写入 volatile 变量之前的所有状态更改都是可见的?如果我们改变 1000 件事情会怎样?

【问题讨论】:

    标签: java multithreading concurrency volatile memory-barriers


    【解决方案1】:

    JMM 给出的保证是 - 如果线程 1 写入一个 volatile 变量,然后线程 2 读取相同的 volatile 变量,那么线程 2保证可以看到 all 线程 1 在写入易失性变量之前所做的更改(包括对非易失性变量所做的更改)。这是一个强有力的保证,并且每个人都同意。

    但是,保证仅适用于线程 2 看到的内容。 您可能还有另一个线程,线程 3,它可能看不到由线程 1 设置的非易失性字段的最新值(线程 3 可能具有并且被允许缓存值对于那些非易失性领域)。只有在线程 3 读取相同的 volatile 后,才能保证看到来自线程 1 的非易失性写入

    【讨论】:

      【解决方案2】:

      在上面的例子中,我们写入了一个 volatile 变量,这会导致一个 mfence,进而将所有挂起的存储缓冲区/加载缓冲区刷新到主缓存...

      这是正确的。

      使其他缓存行无效。

      这是不正确的,或者至少是误导性的。使其他高速缓存行无效的不是写内存屏障。它是在其他处理器中运行的读取内存屏障使每个处理器的缓存行无效。内存同步是线程写入和从volatile变量读取的其他线程之间的协同动作。

      Java 内存模型实际上保证只有读取写入的 same 变量才能保证更新该变量。现实情况是,所有内存缓存行在跨越写内存屏障时被刷新,并且所有内存缓存行在跨越读内存屏障时都无效 - 无论正在访问的变量如何。

      但是,可以优化非易失性字段并将其存储在寄存器中吗?那么,我们如何确保写入 volatile 变量之前的所有状态更改都是可见的?如果我们改变 1000 件事情会怎样?

      根据this documentation(和其他人),内存屏障也会导致编译器生成也刷新寄存器的代码。引用:

      ... 而使用 barrier() 时,编译器必须丢弃它当前缓存在任何机器寄存器中的所有内存位置的值。

      【讨论】:

      • “它是在其他处理器中运行的读取内存屏障使每个处理器的缓存行无效” - 但是读取 volatile 在 x86 上不会生成栅栏...?
      • 你能引用那个@Bober02 的消息来源吗?
      • 虽然这在技术上可能是正确的@Bober02,但魔鬼在细节中,所以只是说这似乎是说 volatile 读取是“免费的”并且它们不在 x86 上。无论发出什么汇编代码,编译器或其他一些实体都需要实现内存规范。所以当我使用“内存屏障”时,我说的是 JMM,而不是硬件在做什么。见:brooker.co.za/blog/2012/09/10/volatile.html
      • 这并不完全正确:“Java 内存模型实际上保证只有读取写入的相同变量才能保证更新该变量。” - 虽然只有读取写入的 same volatile 变量,在两个线程之间建立了先发生的关系,效果是 everything(所有内存写入在一个线程中 write 到 volatile 变量之前发生的任何字段,无论是否 volatile ,似乎都发生在 read 中的 volatile 变量之前其他线程。请参阅 JLS 第 17.4.5 节
      • 就内存模型@ErwinBolwidt 而言这是正确的,但就模型的实现而言并非如此。试着想象一下,如果 JVM 必须以某种方式配对它们,它会如何实现内存屏障。
      【解决方案3】:

      编译器负责确保保留内存模型的语义。在您的示例中,编译器将确保在 volatile 写入之前将任何跨线程可见值写入内存(在 x86 上,普通存储足以满足此目的)。

      【讨论】:

        【解决方案4】:

        易失性变量具有同步的可见性特征,但不具有原子性特征。这意味着线程将自动查看 volatile 变量的最新值。

        您只能在有限的一组情况下使用 volatile 变量而不是锁。 volatile 变量必须满足以下两个条件才能提供所需的线程安全:

        对变量的写入不依赖于其当前值。

        该变量不参与与其他变量的不变性。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-09-28
          • 2015-11-07
          • 2023-03-21
          • 2023-02-10
          • 1970-01-01
          • 2011-03-07
          相关资源
          最近更新 更多