今天偶然见到一位群友的问题
synchronized
是怎么保证可见性的?
先说一下这个问题的答案,
java 在锁释放的时候,通过jmm将缓存中的值, 刷新到内存当中, 以此来保证了数据的可见性
其实这个问题, 我们在群里延伸了很多内容, 从JMM到MESI, 从 Java 代码到汇编指令. 因为讨论的内容较多较深, 固有这篇随笔记, 这一切要感谢提出 synchronized
是怎么保证可见性的这位同学;
今天讨论的内容主要就是围绕 volatile 展开的, 主要的是讨论了一下 volatile 的实现, 对于 volatile 的实现, 这里有一个很好的文章来理解 @何登成 的《C/C++ volatile 关键字剖析》
在 C/C++ 中,volatile具有易变性,是因为当读取 volatile 关键字修饰的变量的时候,不会读取寄存器的值,而是通过缓存重新加载到寄存器,然后再去读取这个值。同时volatile关键字还具有不可优化的特性,主要体现在不可用常量替换。必须要取缓存中读取寄存器在使用。接下来的就是 volatile 关键字的顺序性问题,在 C/C++中, 普通操作与volatile操作可能出现乱序的情况. 而 volatile操作与volatile操作在不同的处理器情况下, 仍然可能出现乱序的情况. 这是因为 cpu 要对指令优化可能进行排序, 在 x86 上, 读指令就有可能提前到写指令之前. storeload 乱序.
volatile 的顺序性问题, 在 C/C++中对于不同的处理器, 仍然会出现乱序的情况, volatile 决定不了. (解决的办法就是实现顺序一致规则 happends-before 语义)
Java 中, volatile 关键字继承了 C/C++中的可见性之外, 又进行了一个操作, 使得 volatile 关键字拥有了顺序性. 那就是内存屏障. 最关键的就是 StoreLoad 屏障, 解决了在X86处理器上的写读重排序的问题
X86处理器仅会对写-读操作做重排序。X86不会对读-读、读-写和写-写操作做重排序
在 Java 层面, 虚拟机通过插入内存屏障来实现 volatile, 这部内容可以在虚拟机源码中 OrderAccess 类中找到, 一共是4个方法, 分别对应到4个内存屏障中.
而在CPU层面, 具体的执行则是使用了 Lock 前缀指令关于 Lock 前缀的指令作用
关于 Lock 前缀, 这里简单总结一下. 在执行前增加 Lock 前缀