之前听公司讲座说到的设计模式,经典的懒汉式单例模式会有重排序问题,当时不是很理解,后来深入学了JVM终于恍然大悟,这里做个总结分享。
重排序排序的就是操作指令的顺序,改变了指令的执行顺序。重排序首先要知道字节码.class文件,它就是JavaC编译后的那个字节码文件,它里面有操作指令的执行顺序,程序计数器就是根据字节码的操作指令的顺序进行寻址查找属性和方法进行操作。JVM会自行判断,把速度快的逻辑简单的代码先执行。
但是有的情况下,JVM会自己对执行指令进行一些优化,比如我们知道for循环重复读取一个值,它会直接从缓存中读取,这时候值就算发生改变JVM也没及时发现。就要加volatile关键字修饰那个属性值,volatile关键字简单来说,就是不让JVM进行优化。
重排序问题也是JVM做的优化,但是变成了好心做坏事。
比如单例模式下,我们都知道创建对象过程中,分配空间是在加载阶段,而里面有什么属性什么方法,都是初始化阶段才有的。JVM优化后就可能直接return了一个全部是null的只是分配了内存空间但是没有初始化的对象。
来看简单的懒汉式单例模式例子:
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
比如有个方法A先执行,再给属性B附值,最后B return,但是A执行复杂,JVM觉得A可能不影响B的return,就先附值return B,再慢慢跑A方法,这就是操作指令的执行顺序变了(字节码文件可以看到的)。
所以,懒汉式单例模式必须加入双重锁校验和volatile关键字,使得 == null 判断每次都会去读取最新的singleton,而不是读缓存中的那个,这就解决了JVM的重排序问题。
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
具体的测试代码在网上也很多,都可以证明JVM的重排序是真实存在的,而且一发生了问题就很难排查,除非不怕辛苦一行行看字节码操作指令的一行行执行。
鞠躬。