【问题标题】:Does the Java Memory Model (JSR-133) imply that entering a monitor flushes the CPU data cache(s)?Java 内存模型 (JSR-133) 是否暗示进入监视器会刷新 CPU 数据缓存?
【发布时间】:2010-06-16 14:33:37
【问题描述】:

Java 内存模型让我有些不解(如果我什至正确理解所有内容)。如果有两个线程 A 和 B,则无法保证 B 会看到 A 写入的值,除非 A 和 B 在同一监视器上同步。

对于任何保证线程间缓存一致性的系统架构,都没有问题。但是如果架构在硬件上不支持缓存一致性,这实质上意味着每当一个线程进入一个监视器,之前所做的所有内存更改都必须提交到主内存,并且缓存必须失效。它需要是整个数据缓存,而不仅仅是几行,因为监视器没有信息它保护内存中的哪些变量。 但这肯定会影响任何需要频繁同步的应用程序的性能(尤其是诸如具有短运行作业的作业队列之类的东西)。那么,Java 能否在没有硬件缓存一致性的架构上运行得相当好呢?如果不是,为什么内存模型不对可见性做出更强有力的保证?如果该语言需要由监视器保护的信息,那不是更有效吗?

在我看来,内存模型为我们提供了两全其美的条件,即绝对需要同步,即使在硬件中保证缓存一致性,另一方面在不连贯的架构上性能不佳(完全缓存刷新)。那么它不应该更严格(需要由监视器保护的信息)还是更多地丢失并将潜在平台限制为缓存一致的架构?

就目前而言,这对我来说没有太大意义。有人能解释一下为什么选择这个特定的内存模型吗?


编辑:回想起来,我使用严格和失败是一个糟糕的选择。我用“严格”表示保证较少的情况,而“失败”表示相反的情况。为避免混淆,最好用更强或更弱的保证来说话。

【问题讨论】:

  • 有一点需要说明的是“在同一台显示器上同步”这句话。据我了解,同一监视器上的同步保证了该监视器周围的执行顺序。但是,就内存缓存而言,获取 any 同步块会强制缓存重新加载,而释放 any 锁会强制刷新工作 CPU 内存中的所有写入。
  • 您想到了哪个特定的拱门?

标签: java multithreading synchronization memory-model cpu-cache


【解决方案1】:

绝对需要同步,即使 如果保证缓存一致性 硬件

是的,但是您只需要针对 Java 内存模型进行推理,而不是针对您的程序恰好在其上运行的特定硬件架构。另外,这不仅与硬件有关,编译器和 JIT 本身可能会重新排序导致可见性问题的指令。 Java 中的同步构造在所有可能的代码转换级别(例如编译器/JIT/CPU/缓存)中始终如一地解决可见性和原子性问题。

另一方面表现不佳 关于不连贯的架构(完整 缓存刷新)

也许我误解了 s/t,但是对于不连贯的架构,无论如何你都必须同步关键部分。否则,由于重新排序,您将遇到各种竞争条件。我不明白为什么 Java 内存模型会使事情变得更糟。

不应该更严格吗(要求 信息由 a 保护 监视器)

我认为根本不可能告诉 CPU 刷新缓存的任何特定部分。编译器能做的最好的事情是发出内存栅栏,让 CPU 决定缓存的哪些部分需要刷新——它仍然比我想的更粗粒度。即使可以进行更细粒度的控制,我认为这会使并发编程变得更加困难(已经够难了)。

AFAIK,Java 5 MM(就像 .NET CLR MM)比 x86 和 IA64 等常见架构的内存模型更“严格”。因此,它使关于它的推理相对简单。然而,它显然不应该提供更接近顺序一致性的 s/t,因为这会显着损害性能,因为可以应用的编译器/JIT/CPU/缓存优化更少。

【讨论】:

  • 我没有考虑过重新排序问题,但是如果需要重新排序边界,只需访问一个 volatile 字段就可以提供一个不需要同步的字段。如果内存模型包括关于非易失性字段可见性的保证,则单个易失性字段可用于在两个线程之间传达任意数量的非易失性字段的有效性(尽管以轮询该易失性字段为代价)。
  • 关于不连贯的架构,对于易失性字段,JIT 可以使用绕过缓存的指令,或者如果这些指令不存在,则只需刷新覆盖该字段的单个缓存行。使用同步的语义,没有限制将哪些字段发布到另一个线程,因此我得出结论,在这种情况下需要刷新整个缓存。这就是我的烦恼。刷新整个缓存(可能是兆字节的脏缓存),即使只需要将几个字段发布到另一个线程似乎效率低下(当然这在很大程度上取决于应用程序)
  • 我不得不承认,我只熟悉一个处理器系列,其中一个包括刷新/使单行无效的指令,以及一个内存页面(如 MMU 页面)。由于这是执行 DMA 的设备驱动程序的性能相关方面,我会假设任何合理的架构都有办法实现这一点。但我可能对这个假设完全错误。
  • @Durandal "只需访问一个 volatile 字段就可以提供一个不需要同步的字段" 访问 volatile 一种同步。跨度>
【解决方案2】:

现有架构保证缓存一致性,但不保证顺序一致性——两者是不同的。自序。不保证一致性,硬件允许某些重新排序,您需要关键部分来限制它们。关键部分确保一个线程写入的内容对另一个线程可见(即,它们防止数据竞争),并且它们还防止经典的竞争条件(如果两个线程增加同一个变量,你需要每个线程读取当前值和写入新值是不可分割的)。

此外,执行模型并不像您描述的那样昂贵。在大多数现有架构上,它们是缓存一致但不是顺序一致的,当您释放锁时,您必须将挂起的写入刷新到内存,并且当您获得一个时,您可能需要做一些事情来确保未来的读取不会读取过时的值 -大多数情况下,这意味着只是防止读取移动得太早,因为缓存保持一致;但仍然不能移动读取。

最后,您似乎认为 Java 的内存模型 (JMM) 很奇特,而其基础现在相当先进,类似于 Ada、POSIX 锁(取决于对标准的解释),和 C/C++ 内存模型。您可能想阅读 JSR-133 手册,其中解释了如何在现有架构上实现 JMM:http://g.oswego.edu/dl/jmm/cookbook.html

【讨论】:

    【解决方案3】:

    答案是大多数多处理器都是缓存一致的,包括大型 NUMA 系统,这几乎是哪个?总是 ccNUMA。

    我认为您对在实践中如何实现缓存一致性有些困惑。首先,缓存可能与系统上的其他几个事物保持一致/不一致:

    • 设备
    • (内存修改者)DMA
    • 数据缓存与指令缓存
    • 其他内核/处理器上的缓存(这个问题是关于)
    • ...

    必须采取一些措施来保持一致性。在使用设备和 DMA 时,在缓存与 DMA/设备不一致的架构上,您将绕过缓存(可能还有写缓冲区),或者在涉及 DMA/设备的操作周围使缓存无效/刷新。

    同样,在动态生成代码时,可能需要刷新指令缓存。

    当涉及到 CPU 缓存时,一致性是使用一些一致性协议来实现的,例如 MESI、MOESI ......这些协议定义了在缓存之间发送的消息以响应某些事件(例如:对其他缓存的无效请求当修改非独占高速缓存行时,...)。

    虽然这足以维持(最终)一致性,但它不能保证顺序,或者其他 CPU 可以立即看到更改。然后,还有写缓冲区,这会延迟写入。

    因此,每个 CPU 架构都提供排序保证(例如,对齐存储之前的访问不能在存储之后重新排序)和/或提供指令(内存屏障/栅栏)来请求此类保证。最后,进入/退出监视器并不需要刷新缓存,但可能需要耗尽写入缓冲区和/或等待读取结束。

    【讨论】:

    • 我知道,由于 x86 占主导地位,问题更具理论性质,但如果假设硬件支持一致性,Java 内存模型的定义似乎过于保守。 DMA 设备的示例并没有真正阐明该主题,因为任何此类设备的驱动程序都将负责在需要时刷新缓存 - 与 Java 不同,驱动程序确切地知道哪个内存区域是会受到影响。
    • 关于设备和 DMA 的重点是解释“不连贯”有很多含义,这可能会误导人们认为具有连贯缓存的架构是不连贯的。
    • 关于缓存一致性,正如我解释的那样,缓存并不是立即一致的,但协议可以确保最终的一致性。栅栏可防止由于写入缓冲区、失效队列、银行缓存等导致的排序和可见性问题...
    【解决方案4】:

    JVM 可以访问的缓存实际上只是 CPU 寄存器。由于它们的数量并不多,因此在监视器退出时刷新它们并不是什么大问题。

    编辑:(通常)内存缓存不受 JVM 控制,JVM 不能选择读/写/刷新这些缓存,所以在这个讨论中忘记它们

    假设每个 CPU 有 1,000,000 个寄存器。 JVM 愉快地利用它们进行疯狂的快速计算 - 直到它遇到监视器进入/退出,并且必须将 1,000,000 个寄存器刷新到下一个缓存层。

    如果我们生活在那个世界中,Java 必须足够聪明地分析哪些对象不共享(大多数对象不共享),或者它必须要求程序员这样做。

    java 内存模型是一种简化的编程模型,它允许普通程序员制定好的多线程算法。我所说的“简化”是指全世界可能有 12 个人真正阅读了 JLS 的第 17 章并真正理解了它。

    【讨论】:

    • 我不认为这是正确的。 CPU 还具有位于主内存en.wikipedia.org/wiki/CPU_cache 之前的兆字节顺序的内存高速缓存。问题是当此缓存将其脏页刷新到主内存并使主内存中已更新的页面无效时。
    • 缓存和寄存器的语义是不同的——寄存器是线程本地的,而缓存可以在线程之间共享(甚至可能在进程之间,取决于架构)。
    • 兄弟们,你说的内存缓存不是JVM可编程的,所以和我们讨论无关。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-07-10
    • 1970-01-01
    • 2016-11-19
    • 1970-01-01
    • 2018-11-24
    • 2015-05-26
    • 1970-01-01
    相关资源
    最近更新 更多