0%

内存栅栏

什么是内存栅栏

内存栅栏(Memory Barriers),是让一个CPU处理单元中的内存状态对其它处理单元可见的一项技术。

内存栅栏提供了两个功能

  • 确保从另一个CPU来看栅栏的两边的所有指令都是正确的程序顺序,而保持程序顺序的外部可见性;
  • 实现内存数据可见性,确保内存数据会同步到CPU缓存子系统。

为什么需要内存栅栏

对主存的一次访问一般花费硬件的数百次时钟周期。为了减少这种操作,CPU通过使用Cache来达到高效获取数据的目的。然后Cache为了提高性能,会对指令进行重排序。
当重排序对最终的结果没有影响的时候,这种优化是有益的。但是当多线程共享数据时,重排序将导致错误的结果。所以为了在共享变量的情况下依然可以使用指令重排序,产生了内存栅栏来保证程序的正确性。

内存栅栏是怎么实现的

在底层,内存栅栏是一组指令,一般包括Store Barrier、Load Barrier和Full Barrier。

几乎所有的处理器至少支持一种粗粒度的屏障指令,通常被称为“栅栏(Fence)”,它保证在栅栏前初始化的load和store指令,能够严格有序的在栅栏后的load和store指令之前执行。

不同的CPU架构有不同的实现方式,以X86为例:

  • Store Barrier,强制所有在store屏障指令之前的store指令,都在该store屏障指令执行之前被执行,并把store缓冲区的数据都刷到主存
  • Load Barrier,强制所有在load屏障指令之后的load指令,都在该load屏障指令执行之后被执行,并且一直等到load缓冲区被该CPU读完才能执行之后的load指令。
  • Full Barrier,复合了load和store屏蔽指令。
    无论在何种处理器上,这几乎都是最耗时的操作之一(与原子指令差不多,甚至更消耗资源),所以大部分处理器还会支持更细粒度的屏障指令。

下图是CPU的Local Memory与主存的通信过程:

Java中内存栅栏的使用

Java内存模型中volatile变量在写操作之后会插入一个store屏障,在读操作之前会插入一个load屏障。一个类的final字段会在初始化后插入一个store屏障,来确保final字段在构造函数初始化完成并可被使用时可见。

内存栅栏对性能的影响

内存栅栏阻止了 CPU 很多隐式的内存延迟技术的执行,因此是有性能损耗的,不过在上层看来这种损耗并不大。在合适的时候使用内存栅栏,仍然是一种高效的做法。

Reference

http://mechanical-sympathy.blogspot.jp/2011/07/memory-barriersfences.html
http://ifeve.com/memory-barriers-or-fences/
http://www.infoq.com/cn/articles/memory_barriers_jvm_concurrency

觉得不错,就打赏一下吧