pde*_*eva 12 java concurrency jvm java-memory-model
我知道JVM内存模型是针对CPU的最低公分母而设计的,因此它必须假设JVM可以运行的最弱模型(例如ARM).
现在,考虑到x64具有相当强大的内存模型,假设我知道我的程序只能运行在64位x86 CPU上,我可以忽略哪些同步实践?当我的程序通过虚拟化运行时,这也适用吗?
示例:
众所周知,JVM的内存模型需要同步对long和double的读/写访问,但可以假设其他32位原语(如int,float等)的读/写是原子的.
但是,如果我知道,我是一个64位的x86机器上运行,我可以忽略使用上多头锁/双打知道CPU将原子读/写64个值,只是让他们挥发物(像我会整型/花车)?
Stu*_*rks 15
我知道JVM内存模型是针对CPU的最低公分母而设计的,因此它必须假设JVM可以运行的最弱模型(例如ARM).
那不对.JMM源于各种竞争力量之间的妥协:对较弱内存模型的渴望,以便程序可以在具有弱内存模型的硬件上更快; 希望允许某些优化的编译器编写者的愿望; 并希望并行Java程序的结果是正确和可预测的,并且如果可能的话(!)可以被Java程序员理解.有关内存模型问题的一般概述,请参阅Sarita Adve的CACM文章.
考虑到x64具有相当强大的内存模型,假设我知道我的程序只能在[x64] CPU上运行,我可以忽略哪些同步实践?
没有.问题在于内存模型不仅适用于底层硬件,还适用于执行程序的JVM,实际上也适用于JVM的JIT编译器.编译器可能决定应用内存模型中允许的某些优化,但如果您的程序基于底层硬件对内存行为做出无根据的假设,那么您的程序将会中断.
您询问了x64和原子64位写入.可能在x64机器上不会发生任何单词撕裂.我怀疑任何JIT编译器都会将64位值作为优化分解为32位写入,但您永远不会知道.但是,您似乎不太可能使用此功能来避免程序中的同步或易失性字段.如果没有这些,对这些变量的写入可能永远不会对其他线程可见,或者它们可能随意地相对于其他写入被重新排序,可能导致程序中的错误.
我的建议是首先正确应用同步以使程序正确.你可能会感到惊喜.同步操作已经过大量优化,在常见情况下可以非常快.如果您发现存在瓶颈,请考虑使用锁定拆分,使用挥发性或转换为非阻塞算法等优化.
UPDATE
OP已经将问题更新为更具体的使用volatile而不是锁和同步.
事实证明,volatile不仅具有内存可见性语义.它还使得long和double访问原子,而volatile这些类型的非变量则不是这种情况.参见JLS第17.7节.您应该能够依赖于volatile在任何硬件上提供原子性,而不仅仅是x64.
虽然我在这里,有关Java内存模型的更多信息,请参阅Aleksey Shipilev的JMM Pragmatics谈话记录.(Aleksey也是JMH的家伙.)这次演讲中有很多细节,还有一些有趣的练习来测试一个人的理解.这个话题的一个总体结论是依靠一个人关于内存模型如何工作的直觉通常是错误的,例如在缓存行或写缓冲区方面.JMM是关于内存操作和各种约束(同步,发生在之前等)的形式主义,它们决定了这些操作的顺序.这可能会产生非常违反直觉的结果.试图通过考虑特定的硬件属性来超越JMM是不明智的.它会回来咬你.
始终包含JVM内存模型声明需要它们的内存屏障,然后让JVM在可以用于不同平台时对其进行优化.
知道你只在x86 CPU上运行并不意味着你可以放弃使用内存屏障.除非您知道您将只运行单核x86 cpus;)在今天的多核心世界中,没有人真正知道.
为什么?因为java内存模型有两个主要问题.
如果没有内存屏障,其他内核可见的操作顺序可能会变得非常混乱; 即使x86提供更强大的保证,这也是如此.x86只有在数据进入cpu缓存后才能确保一致性,并且虽然它的排序保证非常强大,但只有在Hotspot告诉CPU写出缓存时才会启动它们.
如果没有volatile/synchronized,则由编译器(javac和hotspot)决定何时执行这些写操作以及按什么顺序执行.他们决定在寄存器中保持数据长时间是完全有效的.一旦交叉了易失性或同步的内存屏障,JVM就会知道告诉CPU将数据发送到缓存.
正如Doug Lea在JSR-133 Cookbook中记录的那样,大多数x86障碍都被简化为无操作指令,以保证订购.因此,JVM将使我们的指令尽可能高效.代码到Java内存模型,让Hotspot发挥其神奇作用.如果Hotspot可以证明不需要synchronized,则可以完全删除它.
最后,双重检查的锁定模式也被证明在多核x86上被打破了; 尽管有更强的记忆保证.这样的一些不错的细节是他的C++ writen由巴托斯卢斯基博客这次又具体到Java 这里