1 写缓存区
- 现代的处理器使用写缓冲区临时保存向内存写入的数据。
- 优点:写缓冲区可以保证指令流水线持续运行,它可以避免由于处理器停顿下来等待向内存写入数据而产生的延迟。同时,通过以批处理的方式刷新写缓冲区,以及合并写缓冲区中对同一内存地址的多次写,减少对内存总线的占用。
- 不足:虽然写缓冲区有这么多好处,但每个处理器上的写缓冲区,仅仅对它所在的处理器可见。
- 影响:这个特性会对内存操作的执行顺序产生重要的影响:处理器对内存的读/写操作的执行顺序,不一定与内存实际发生的读/写操作顺序一致!
2 现代的处理器都会允许对写-读操作进行重排序
- 由于写缓冲区仅对自己的处理器可见,它会导致处理器执行内存操作的顺序可能会与内存实际的操作执行顺序不一致。
- 由于现代的处理器都会使用写缓冲区,因此现代的处理器都会允许对写-读操作进行重排序。
具体示例
假设处理器A和处理器B按程序的顺序并行执行内存访问,最终可能得到x=y=0的结果。
- 这里处理器A和处理器B可以同时把共享变量写入自己的写缓冲区(A1,B1),然后从内存中读取另一个共享变量(A2,B2),最后才把自己写缓存区中保存的脏数据刷新到内存中(A3,B3)。当以这种时序执行时,程序就可以得到x=y=0的结果。
- 从内存操作实际发生的顺序来看,直到处理器A执行A3来刷新自己的写缓存区,写操作A1才算真正执行了。虽然处理器A执行内存操作的顺序为:A1→A2,但内存操作实际发生的顺序却是A2→A1。此时,处理器A的内存操作顺序被重排序了(处理器B的情况和处理器A一样)
3 常见处理器允许的重排序类型
- 单元格中的“N”表示处理器不允许两个操作重排序,“Y”表示允许重排序。
- 常见的处理器都允许Store-Load重排序;
- 常见的处理器都不允许对存在数据依赖的操作做重排序。
- 由于ARM处理器的内存模型与PowerPC处理器的内存模型非常类似,
4 java内存屏障
为了保证内存可见性,Java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序。
JMM把内存屏障指令分为4类
-
StoreLoad Barriers
是一个“全能型”的屏障,它同时具有其他3个屏障的效果。 - 现代的多处理器大多支持该屏障(其他类型的屏障不一定被所有处理器支持)。
- 执行该屏障开销会很昂贵,因为当前处理器通常要把写缓冲区中的数据全部刷新到内存中(Buffer Fully Flush)。
参考
《java并发编程的艺术》