【Java】双重检查锁模式的单例模式(DCL懒汉式)

先给出代码:

class Cat {
    
    
    private String name;
    //volatile 能够防止指令重排造成的问题
    private volatile static Cat cat; //声明唯一实例
    
    private Cat(String name) {
    
    
        this.name = name;
    }
    
    public static Cat getInstance() {
    
    
        if (cat == null) {
    
     //保证加锁之前只有一个对象
            synchronized (Cat.class) {
    
     //获取锁
                if (cat == null) {
    
    
                    cat = new Cat("DCL懒汉猫");
                }
            }
        }
        return cat;
    }
}

单例模式的特点:

  1. 单例类只能有一个实例对象;
  2. 单例类必须自己创建这个实例;
  3. 单例类必须提供获取之一实例的方法:getInstance()。

两次判断对象是否为 null 的目的分别是什么?

最外层的 if 判断很好理解嘛,如果对象已经不为空了,已经创建过了,那您就甭费劲抢锁了,直接拿着实例返回吧。
那为什么在抢到锁之后还要再加一层判断呢?大家来考虑这样一种情况:
两个线程 A 和 B 来获取对象,此时对象还没被创建,A 和 B 都通过了第一层判断,两个线程开始竞争锁,A 抢到了锁,因此 B 进入同步队列阻塞等待。**当 A 创建完对象并且释放了锁,B 拿到了锁,又去执行 new Cat(“DCL懒汉猫”),此时就创建了两个对象。**因此,必须在加锁之后再来一次判断,才能保证只创建一次对象。

为什么要给实例对象添加 volatile 关键字呢?

这是因为编译器在编译时会进行指令重排,而 volatile 可以禁止指令重排
对象的创建大概有这么三个步骤(从指令层面来看啊,从JVM来讲具体还有很多步骤):

  1. 分配内存空间
  2. 执行构造方法,初始化对象
  3. 把这个对象指向这个空间

正常是按 123 的顺序执行,但由于指令重排的存在,可能会存在 A 线程按照132 执行,当执行到 3 时,线程 B 来取对象,就会得到空值。因此必须给单例对象加上 volatile 关键字。

最后

其实DCL懒汉式在 Java 里面也不是绝对安全的,可以通过反射区去破坏它。可以通过枚举类创建绝对安全的单例模式,枚举类不仅能防止反射,还能防止反序列化去创建对象。

猜你喜欢

转载自blog.csdn.net/weixin_43390123/article/details/126431501