/** * 单例模式之双检锁 * @author ring2 * 懒汉式升级版 */ public class Singleton3 { private static volatile Singleton3 instance; private Singleton3() {} public static Singleton3 getInstance() { //首先判断是否为空 if(instance==null) { //可能多个线程同时进入到这一步进行阻塞等待 synchronized(Singleton3.class) { //第一个线程拿到锁,判断不为空进入下一步 if(instance==null) { /** * 由于编译器的优化、JVM的优化、操作系统处理器的优化,可能会导致指令重排(happen-before规则下的指令重排,执行结果不变,指令顺序优化排列) * new Singleton3()这条语句大致会有这三个步骤: * 1.在堆中开辟对象所需空间,分配内存地址 * 2.根据类加载的初始化顺序进行初始化 * 3.将内存地址返回给栈中的引用变量 * * 但是由于指令重排的出现,这三条指令执行顺序会被打乱,可能导致3的顺序和2调换 * 👇 */ instance = new Singleton3(); } } } return instance; } }
由于指令重排导致3,2的顺序调换以及处于多线程场景,会导致以下问题的出现:
首先第一个线程执行到了3号指令(instance变量被分配了地址,不为null了),但对象未初始化。此时!第一个或者第二个if语句进行判断时结果为true,自然而然在使用instance时会出错。
解决的方法便是在instance变量上加上volatile关键字,加上volatile关键字后会禁止该变量的指令重排,从而达到线程安全。