CPU禁止指令重排的底层实现
一、CPU指令重排
CPU为了提高效率会对指令进行重排序,以适合cpu的顺序运行。但是指令重排会遵守As-if-serial的规则,就是所有的动作(Action)都可以为了优化而被重排序,但是必须保证它们重排序后的结果和程序代码本身的应有结果是一致的。
所以这种情况在单线程中不会出现什么问题。而对于多线程,这个规则就失效了,所以可能会导致结果出现问题。
二、禁止指令重排技术
1、禁止指令重排:内存屏障,也叫内存栅栏。是一种屏障指令,cpu指令;
2、原子指令。
三、内存屏障
1、CPU内存屏障:两条指令,如果不想让它重排,在两条指令中间加一道屏障。即 屏障两侧的写指令不能重排
sfence:save| 在sfence指令前的写操作当必须在sfence指令后的写操作前完成
lfence:load| 在lfence指令前的写操作当必须在lfence指令后的写操作前完成
mfence:mix| 在mfence指令前的写操作当必须在mfence指令后的写操作前完成
除了内存屏障,也可以使用原子指令,如x86上的"lock..." lock后面的指令不允许重排序
2、JVM级别规范
LoadLoad屏障:
对于这样的语句Load1;LoadLoad;Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕
SroreStore屏障:
对于这样的语句Store1;SroreStore;Store2,在Store2及后续写入操作要读取的数据被访问前,保证Store1的写入操作对其他处理器可见
LoadStore屏障:
对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
StoreLoad屏障:
对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。
四、指令重排技术的具体实现
1、volatile实现细节
volatile关键字:可以解决可见性,又可以禁止指令重排。
1.1、字节码层面
Class文件上加了ACC_VOLATILE标识
1.2、JVM层面
读到ACC_VOLATILE时,在内存区的读写 都加屏障
1.3、OS 操作系统层面
windows lock 指令实现 | MESI实现
五、拓展
1、synchronized可以保证有序性,无法禁止指令重排。
synchronized通过排他锁的方式就保证了同一时间内,被synchronized修饰的代码是单线程执行的。所以这就满足了as-if-serial语义的一个关键前提,那就是单线程,因为有as-if-serial语义保证,单线程的有序性就天然存在了。
2、Synchronized实现细节
synchronized关键字:可以解决原子性
2.1、字节码层面
对于加了synchronized关键字的代码块,Class文件上会出现monitorenter、monitorexit指令,保证有序性
2.2、JVM层面
C/C++ 调用操作系统底层的同步机制
2.3、OS操作系统层面
windows lock 指令实现
六、总结
volatile和synchronized都可以实现有序性。但实现原理不同:
volatile 靠插入内存屏障指令防止其后面的指令跑到它前面去了
synchronized靠操作系统内核互斥锁实现的,退出代码块时,会刷新变量至主内存。但synchronized代码块的内容可以发生指令重排。