有些东西让我对Java内存模型感到困惑(如果我甚至理解正确的话)。如果有两个线程A和B,则无法保证B将看到A写入的值,除非A和B在同一监视器上同步。
对于保证线程之间缓存一致性的任何系统架构,都没有问题。但是,如果架构不支持硬件中的缓存一致性,这实质上意味着每当线程进入监视器时,之前所做的所有内存更改都必须提交到主内存,并且缓存必须无效。并且它需要是整个数据缓存,而不仅仅是几行,因为监视器没有信息它保存的内存中的变量。 但这肯定会影响任何需要频繁同步的应用程序的性能(特别是像短时间运行的作业队列这样的事情)。那么Java在没有硬件缓存一致性的架构上是否能够很好地工作?如果没有,为什么内存模型不能提供更强的可见性保证?如果语言需要监视器保护的信息,那会不会更有效率呢?
正如我所看到的,内存模型给我们带来了两个世界中最糟糕的,绝对需要同步,即使硬件中的高速缓存一致性得到保证,另一方面非完整体系结构上的性能不佳(完全高速缓存刷新)。那么它是不是应该更严格(需要监视器保护的信息)还是更多地丢失和限制缓存一致的架构的潜在平台?
就像现在一样,它对我来说没有多大意义。有人可以清楚为什么选择这个特定的记忆模型吗?
答案 0 :(得分:6)
绝对需要同步,甚至 如果保证缓存一致性 硬件
是的,但是你只需要反对Java内存模型,而不是针对程序碰巧运行的特定硬件架构。此外,它不仅涉及硬件,编译器和JIT本身可能会重新排序导致可见性问题的指令。 Java中的同步构造解决了可见性和原子性始终在所有可能的代码转换级别(例如编译器/ JIT / CPU /缓存)。
另一方面,表现不佳 在非连贯的架构上(完整的 缓存刷新)
也许我误解了s / t,但是使用不一致的架构,无论如何都必须同步关键部分。否则,由于重新排序,您将遇到各种竞争条件。我不明白为什么Java Memory Model会让事情变得更糟。
不应该更严格(要求 什么是守卫的信息 监视器)
我认为不可能告诉CPU根据缓存的任何特定部分进行刷新。编译器可以做的最好的事情是发出内存防护,并让CPU决定缓存的哪些部分需要刷新 - 它比你想要的更粗粒度。即使可以进行更细粒度的控制,我认为这会使并发编程变得更加困难(已经很难了)。
AFAIK,Java 5 MM(就像.NET CLR MM一样)比x86和IA64等常见架构的内存模型更“严格”。因此,它使得它的推理相对简单。然而,它显然不应该提供更接近顺序一致性的s / t,因为这会显着损害性能,因为可以应用更少的编译器/ JIT / CPU /缓存优化。
答案 1 :(得分:5)
现有体系结构保证缓存一致性,但它们不保证顺序一致性 - 两者是不同的。自seq。不保证一致性,硬件允许一些重新排序,您需要关键部分来限制它们。关键部分确保一个线程写入的内容对另一个线程可见(即,它们阻止数据竞争),并且它们还会阻止经典的竞争条件(如果两个线程增加了相同的变量,你需要为每个线程读取当前值和写入新值是不可分割的。)
此外,执行模型并不像您描述的那样昂贵。在大多数现有体系结构中,它们是缓存一致但不是顺序一致的,当您释放锁定时,必须清除对内存的挂起写入,当您获得一个时,您可能需要做一些事情以确保将来的读取不会读取过时的值 - 这主要意味着只是防止读取过早移动,因为缓存保持连贯;但仍然不能移动读取。
最后,你似乎认为Java的内存模型(JMM)是特殊的,而现在基础是相当先进的,类似于Ada,POSIX锁(取决于标准的解释),和C / C ++内存模型。您可能希望阅读JSR-133手册,该手册解释了如何在现有架构上实现JMM:http://g.oswego.edu/dl/jmm/cookbook.html。
答案 2 :(得分:4)
答案是大多数多处理器都是缓存一致的,包括大型NUMA系统,几乎?总是ccNUMA。
我认为你对缓存一致性在实践中是如何完成有些困惑。首先,对于系统上的其他几个方面,缓存可能是连贯的/不连贯的:
必须做一些事情才能保持一致性。在使用设备和DMA时,在DMA /设备上具有不一致缓存的体系结构上,您可以绕过缓存(可能还有写缓冲区),或者在涉及DMA /设备的操作周围使缓存无效/刷新。
同样,在动态生成代码时,您可能需要刷新指令缓存。
当谈到CPU缓存时,使用一些一致性协议(例如MESI,MOESI,......)来实现一致性。这些协议定义了在缓存之间发送的消息以响应某些事件(例如:对其他缓存的无效请求)当修改非独占的高速缓存行时,...)。
虽然这足以维持(最终)一致性,但它不保证排序,或者其他CPU立即可以看到更改。然后,还有写缓冲区,它会延迟写入。
因此,每个CPU架构提供排序保证(例如,在对齐的存储在存储之后不能重新排序之前的访问)和/或提供请求这种保证的指令(存储器屏障/栅栏)。最后,进入/退出监视器不需要刷新缓存,但可能需要耗尽写缓冲区,和/或停止等待读取结束。
答案 3 :(得分:1)
JVM可以访问的缓存实际上只是CPU寄存器。因为它们并不多,所以在显示器退出时冲洗它们并不是什么大问题。
编辑:(一般情况下)内存缓存不受JVM控制,JVM无法选择读/写/刷新这些缓存,因此在本次讨论中忘记它们
想象每个CPU有1,000,000个寄存器。 JVM愉快地利用它们进行疯狂的快速计算 - 直到它碰到监视器进入/退出,并且必须将1,000,000个寄存器刷新到下一个缓存层。
如果我们生活在这个世界中,Java必须足够聪明才能分析哪些对象不共享(大多数对象不是),或者它必须要求程序员这样做。
java内存模型是一种简化的编程模型,它允许普通程序员制作好的多线程算法。通过'简化',我的意思是全世界可能有12个人真正阅读了JLS的第17章并且实际上理解它。