2.DCL分析

DCL, 即(Double Check Lock),中文:双重检查锁定

1.问题分析

我们先看看单例模式里面的懒汉式

public class Singleton{
    private static Singleton singleton;
    
    private Singleton(){}
    
    public static Singleton getInstance(){
        if (singleton ==null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

以上是无法保证他是线程的安全性,优化如下:

public class Singleton{
    private static Singleton singleton;
    
    private Singleton(){}
    
    public static synchronize Singleton getInstance(){
        if (singleton ==null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

以上优化就确保了线程的安全性,但是性能非常的低效,导致性能下降,那么我们用DCL双重检查来优化

public class Singleton{
    private static Singleton singleton;
    
    private Singleton(){}
    
    public static  Singleton getInstance(){
        if (singleton ==null){
            synchronize(Singleton.class){
                if (singleton == null){
                singleton  = new Singleton();
                }
            }
        }
        return singleton;
    }
}

以上代码看起来很完美吧,那我们来分析一下:

  1. 如果检查第一个singleton不为null,则不需要执行以下代码,提高了性能
  2. 如果第一个singleton为neull,即使有多个线程同一个时间判断,但是由于synchronize的存在,只会有一个线程能够创建对象
  3. 当第一个获取锁的线程创建完成后singleton对象后,其他的第二次判断singleton一定会为null,则直接返回已经创建好的singleton对象

是不是很完美的代码,逻辑上是完全没有问题的. 但是事实上并不是,上面的逻辑在jvm指令排序是存在问题的.jvm创建实例化对象的三个步骤

memory = allocate();   //1: 分配内存空间
ctorInstance(memory);  //2: 初始化对象
instance = memory;     //3: 将内存空间的地址赋值给对象的引用

但是由于重排序的原因,步骤2,3可能会发生重排序,其过程如下

memory = allocate();   //1: 分配内存空间
instance = memory;     //3: 将内存空间的地址赋值给对象的引用
ctorInstance(memory);  //2: 初始化对象

以上的排序就会导致第二个判断会出错 singleton !=null 但是这时该对象只是个内存地址,并没有初始化实例,所以return的是singleton的内存地址,导致第二个线程获取到的是singleton的内存地址,引用起来就会抛错

此时就知道错误在哪里了

singleton = new Singleton();

知道问题根源,解决方法有两个:

  • 不允许初始化阶段步骤发生重排序
  • 允许重排序,但是不允许其他线程看到这个排序(即单例的饿汉单例模式)

方案一:

基于以上代码修改:将变量singleton声明为volatile

public class Singleton {

    // 通过volatile关键字来确保安全
    private volatile static Singleton singleton;
    private Singleton(){}

    public static Singleton getInstance(){
        if(singleton == null){
            synchronized (Singleton.class){
                if(singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

当singleton声明为volatile后,new实例化的步骤就不会重新排序了

方案二:(使用单例模式的饿汉模式)

原理: 利用ClassLoder的机制,保证初始化instance时只有一个线程,jvm在类初始化阶段会获取一个锁,这个锁可以同步多个线程对一个类的初始化

public class Singleton {

    private static class SingletonHolder{
        public static Singleton singleton = new Singleton();
    }

    public static Singleton getInstance(){
        return SingletonHolder.singleton;
    }
}

原理图:


image

你可能感兴趣的:(2.DCL分析)