2024-03-23  阅读(101)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/mianshi/baodian/detail/1955698867

DCL,Double-Checked Locking, 即双重检查锁定。很多小伙伴在单例模式中用到它,代码如下:

public class Singleton {
    private volatile static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {                       // 1
            synchronized (Singleton.class) {          // 2
                if (instance == null) {               // 3
                    instance = new Singleton();       // 4
                }
            }
        }
        return instance;
    }
}

对于这段代码其实可问的有很多,比如:

  1. 为什么构造函数要使用 private?
  2. 为什么要进行两次 if (instance == null)?
  3. synchronized (Singleton.class) 在这里的作用是什么?
  4. synchronized (Singleton.class) 中为什么要使用 Singleton.class?使用其他的可以么?例如
  5. 变量 instance 为什么要使用 volatile 修饰?

这里我们只关注第 5 个 问题,为什么 变量 instance 要使用 volatile

首先我们看如果不使用 volatile 会有什么影响。我们先看这个过程:

  1. 第一个 if (instance == null),如果为 false,则不需要执行下面的代码了,提高了程序的性能。
  2. 如果 instance == null,即使是多线程,也会因为 synchronized 的存在,只会有一个线程执行下面的代码。
  • 当第一个获得锁的线程创建完成后 singleton对象后,其他的线程也会在第二次判断 singleton一定不会为 null,则直接返回已经创建好的singleton对象。

细看上面的逻辑是没任何问题的,但是大明哥告诉你不加 volatile 就是有问题,那问题出在哪里呢?我们先来复习一下创建对象过程,实例化一个对象要分为三个步骤:

  1. 给 singleton 对象分配内存空间
  2. 调用 Singleton 类的构造函数等,初始化 singleton 对象
  3. 将 singleton 对象指向分配的内存空间,这步一旦执行了,那 singleton 对象就不等于null了

我们知道编译器或 CPU 为了提供程序的执行效率,会对代码和指令进行重排序,上面步骤2、3 可能会发生重排序,那么过程就变成这样了:

  1. 给 singleton 对象分配内存空间
  2. 将 singleton 对象指向分配的内存空间
  3. 调用 Singleton 类的构造函数等,初始化 singleton 对象

如果步骤 2、3发生了重排序就会导致第二个判断(if(singleton != null))会出错,因为它其实仅仅只是一个地址而已,对象还没有完成初始化,所以 return 的 singleton 对象是一个没有被初始化的对象,调用会报错,如下:

所以,DCL 使用 volatile 关键字,是为了禁止指令重排序,避免返回还没完成初始化的 singleton 对象,导致调用报错,也保证了线程的安全。确切地说是,就是使用 volatile防止了Java 对象在实例化过程中的指令重排,确保在对象的构造函数执行完毕之前,不会将 instance 的内存分配操作指令重排到构造函数之外

阅读全文
  • 点赞