我百度翻译了一下“Memory Barriers”,给出的结果是:记忆障碍。让我大吃一惊。实际上,我们在程序中叫它内存屏障。本文将介绍一下java中的内存屏障(Memory Barriers)。
内存屏障(Memory Barrier,或有时叫做内存栅栏,Memory Fence)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。
java内存屏障
java 的内存屏障通常所谓的四种即LoadLoad、StoreStore、LoadStore、StoreLoad 。
- LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
- StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
- LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
- StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。 在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。
有的处理器的重排序规则较严,无需内存屏障也能很好的工作,Java编译器会在这种情况下不放置内存屏障。
volatile语义中的内存屏障
volatile的内存屏障策略非常严格保守,非常悲观且毫无安全感的心态:
- 在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障;
- 在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障;
由于内存屏障的作用,避免了volatile变量和其它指令重排序、线程之间实现了通信,使得volatile表现出了锁的特性。
final语义中的内存屏障
对于final域,编译器和CPU会遵循两个排序规则:
- 新建对象过程中,构造体中对final域的初始化写入和这个对象赋值给其他引用变量,这两个操作不能重排序;(废话嘛)
- 初次读包含final域的对象引用和读取这个final域,这两个操作不能重排序;(晦涩,意思就是先赋值引用,再调用final值)
也就是说必需保证一个对象的所有final域被写入完毕后才能引用和读取。这也是内存屏障的起的作用。
写final域:在编译器写final域完毕,构造体结束之前,会插入一个StoreStore屏障,保证前面的对final写入对其他线程/CPU可见,并阻止重排序。
读final域:在上述规则2中,两步操作不能重排序的机理就是在读final域前插入了LoadLoad屏障。
参考资料
- JVM内存模型、指令重排、内存屏障概念解析
- 内存屏障
- 内存屏障 & Memory barrier
: » Java内存屏障(Memory Barriers)
原创文章,作者:306829225,如若转载,请注明出处:https://blog.ytso.com/251785.html