假设我知道我将在 x64 cpu 上运行,我可以忽略哪些 JVM 同步实践?

2022-09-04 06:42:12

我知道JVM内存模型是为CPU的最低公分母而制作的,因此它必须假设JVM可以运行的CPU的最弱模型(例如ARM)。

现在,考虑到 x64 具有相当强的内存模型,假设我知道我的程序只能在 64 位 x86 CPU 上运行,我可以忽略哪些同步做法?此外,当我的程序通过虚拟化运行时,这是否适用?

示例:
众所周知,JVM 的内存模型需要同步对 long 和 doubles 的读/写访问,但可以假设其他 32 位基元(如 int、float 等)的读/写是原子的。

但是,如果我知道我在64位x86机器上运行,我可以忽略在longs/doubles上使用锁,因为我知道cpu会原子地读取/写入64位值并保持它们易失性(就像我对ints/floats所做的那样)?


答案 1

我知道JVM内存模型是为CPU的最低公分母而制作的,因此它必须假设JVM可以运行的CPU的最弱模型(例如ARM)。

这是不正确的。JMM源于各种竞争力量之间的妥协:对较弱内存模型的渴望,以便程序可以在具有弱内存模型的硬件上更快地运行;希望允许某些优化的编译器编写者的愿望;并希望并行Java程序的结果正确且可预测,并且如果可能的话(!)对于Java程序员来说是可以理解的。请参阅Sarita Adve的CACM文章,了解内存模型问题的一般概述。

考虑到 x64 具有相当强的内存模型,假设我知道我的程序只能在 [x64] CPU 上运行,我可以忽略哪些同步做法?

没有。问题在于,内存模型不仅适用于底层硬件,还适用于执行程序的 JVM,并且在实践中主要适用于 JVM 的 JIT 编译器。编译器可能会决定应用内存模型中允许的某些优化,但是如果您的程序基于底层硬件对内存行为做出不必要的假设,则程序将中断。

您询问了有关 x64 和原子 64 位写入的问题。可能是x64机器上不会发生任何单词撕裂。我怀疑任何JIT编译器都会将64位值分解为32位写入作为优化,但你永远不会知道。但是,似乎不太可能使用此功能来避免程序中的同步或易失性字段。如果没有这些,对这些变量的写入可能永远不会对其他线程可见,或者它们可以相对于其他写入任意重新排序,从而导致程序中的错误。

我的建议是首先正确应用同步以使程序正确。你可能会感到惊喜。同步操作已经过高度优化,在常见情况下可以非常快。如果发现存在瓶颈,请考虑使用锁拆分、使用易失性或转换为非阻塞算法等优化。

更新

OP 更新了问题,使其更具体地说明了如何使用而不是锁和同步。volatile

事实证明,不仅具有内存可见性语义。它还使和访问原子,对于这些类型的非变量来说,情况并非如此。请参阅 JLS 第 17.7 节。您应该能够依靠在任何硬件上提供原子性,而不仅仅是x64。volatilelongdoublevolatilevolatile

虽然我在这里,但有关Java内存模型的更多信息,请参阅Aleksey Shipilev的JMM Pragmatics talk transcript。(阿列克谢也是JMH的人。这个演讲中有很多细节,还有一些有趣的练习来测试一个人的理解。演讲的一个总体结论是,依靠一个人对内存模型如何工作的直觉通常是一个错误,例如在缓存行或写入缓冲区方面。JMM 是关于内存操作和各种约束(与、之前发生等)的形式主义,用于确定这些操作的顺序。这可能会产生非常违反直觉的结果。试图通过考虑特定的硬件属性来超越JMM是不明智的。它会回来咬你。


答案 2

您仍然需要处理线程安全,因此波动性语义和内存栅栏仍然很重要

我的意思是,例如在Oracle Java中,大多数低级同步操作最终都处于不安全状态(docjar.com/docs/api/sun/misc/Unsafe.html#getUnsafe),这反过来又具有一长串本机方法。因此,最终,这些同步实践和许多其他低级操作都被它们所属的JVM封装在一起。x64 与 x86 没有相同的 jvm。

再次阅读您编辑的问题后:加载/存储操作的原子性是这里的一个主题。所以不,你不必担心x64上的原子64位加载/存储。但是,由于这并不是所有同步问题的结束,请参阅其他答案。


推荐