双重检查

public class Singletone{
    private static Instance instance;

    public Instance getInstance(){
        if(instance == null){
            synchronized(Singletone.class){
                if(instance == null){
                    instance = new Instance();
                }
            }
        }
        return instance;
    }
}

问题
instance = new Instance();是由三个步骤组成的:

  • 为对象分配内存
  • 实例化对象
  • 将引用指向对应的内存地址

但是第2,3步可能发生指令重排列,导致先将引用指向一个未实例化对象的内存地址,然后再进行实例化对象。
若此时第二个线程进行第一个非空判断时,则为false,会直接返回还没有实例化对象的内存地址,从而可能产生异常。

解决

  • 禁止指令重排列
  • 允许指令重排列,但是其他线程“不可见”

方案一:基于volatile禁止指令重排列

public class Singletone{
    private volatile static Instance instance;

    public Instance getInstance(){
        if(instance == null){
            synchronized(Singletone.class){
                if(instance == null){
                    instance = new Instance();
                }
            }
        }
        return instance;
    }
}

在成员变量中加入volatile变量,禁止使用new创建对象时的指令重排列。

方案二:基于类初始化的解决方案

public class Singletone{
    private static class InstanceHolder{
        public static Instance instance = new Instance();
    }

    public static Instance getInstance(){
        return InstanceHolder.instance;
    }
}

JVM在类初始化阶段进行类的初始化。在初始化期间,JVM会获取一个锁,从而同步多个线程对同一个类的初始化。

第一个获得锁的线程会完成实例对象的创建,其他线程会直接获取创建的实例对象。

参考:

相关文章:

  • 2022-12-23
  • 2022-12-23
  • 2021-06-09
  • 2022-12-23
  • 2022-12-23
  • 2021-10-03
  • 2021-07-31
  • 2022-12-23
猜你喜欢
  • 2021-04-19
  • 2021-12-02
  • 2022-03-12
  • 2022-12-23
  • 2021-08-25
  • 2021-04-25
  • 2022-12-23
相关资源
相似解决方案