CPU指令重排与volatail关键字
1.概述
在使用单例模式双重检查锁时,我们要对返回的单例实例加上volatail关键字,目的是为了解决CPU指令重排问题,那么什么是CPU指令重排,volatail又是怎么解决CPU指令重排的问题呢?
2.CPU指令重排
CPU指令重排是指CPU为了提高指令执行效率,可能会对指令的执行顺序进行优化,使得(单线程下)指令的实际执行顺序与代码中的顺序不同,但结果是一致的。这种优化是通过乱序执行和缓存读写重排来实现的。
乱序执行指的是CPU可以在不影响最终结果的前提下,通过并行执行、延迟执行等方式改变指令的执行顺序,从而提高执行效率。而缓存读写重排指的是CPU会先进行缓存读写操作,而不是直接对内存进行读写,这也会导致多个CPU缓存的可见性问题。
CPU是先操作自己的缓存,然后更新到内存中,其他CPU在从内存中获取更新的数据,更新到自己的缓存中,由于内存是隔一段时间刷新一次从缓存中获取最新数据而不是实时的,所以产生缓存一致性问题,也就是CPU自己的缓存对其他CPU不可见。这就是CPU缓存可见性问题。
CPU指令重排可能会导致多线程程序出现一些难以排查和修复的问题,例如线程安全问题和死锁等。为了避免这种问题的发生,可以使用volatile关键字来禁止指令重排。volatile关键字可以保证指令的有序执行,从而避免多线程程序中的可见性问题。
3.如何解决CPU指令重排问题?
CPU提供了两个内存屏障指令(Memory Barrier)用于解决上述两个问题:
写内存屏障(写屏障)
在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其它线程可见。强制写入主内幕才能,这种显示调用,CPU就不会因为性能考虑而去对指令重排。
读内存屏障(读屏障)
在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制重新从主内存中加载,让CPU与主内存保持一致,避免了缓存导致的一致性问题。
4.volatile关键字原理
在java中有四种屏障方式,分别是写屏障和读屏障的四种组合方式, volatile正是基于此解决的CPU指令重排问题。
这四种屏障方式如下:
LoadLoad屏障:
在两次读指令中间加入该屏障,使得前者读取完成后,后者才可以开始读取。
StoreStor屏障:
在两次写指令中间加入该屏障,使得前者写入完成后,后者才可以开始写入。
StoreLoad屏障:
在写读指令中间加入该屏障,使得前者写入完成后,后者才可以开始读取。
LoadStore屏障:
在读写指令中间加入该屏障,使得前者读取完成后,后者才可以开始写入。
在一个变量被volatile修饰后,JVM会为我们做两件事:
1.在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障。
2.在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障。