双重检查锁,线程安全

使用时再进行初始化,线程安全

public class SingletonDemo {

    private static volatile SingletonDemo INSTANCE;

    private SingletonDemo() {
    }

    public static SingletonDemo getINSTANCE() {
        if (INSTANCE == null) {
            synchronized (SingletonDemo.class) {
                if (INSTANCE == null) {
                    INSTANCE = new SingletonDemo();
                }
            }
        }
        return INSTANCE;
    }

    public void otherMethod() {
        System.out.println("执行单例方法");
    }
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

​1. 私有静态 volatile 变量​​

private static volatile SingletonDemo INSTANCE;
  • 1.
  • ​​volatile 的作用​​:
    • 禁止指令重排序​​:确保 INSTANCE = new SingletonDemo(); 这一步骤的原子性。对象的初始化分为三步(分配内存空间→初始化对象→将引用指向内存地址),若没有 volatile,JVM 可能重排序为(分配内存→引用赋值→初始化对象),此时其他线程可能访问到未完全初始化的实例。
    • ​​确保可见性​​:当一个线程修改 INSTANCE 后,其他线程能立即看到最新值,避免因缓存不一致导致多次创建实例。

​​2. 私有构造方法​​

private SingletonDemo() {}
  • 1.
  • 目的​​:禁止外部通过 new SingletonDemo() 直接创建实例。
  • ​​关键点​​:是所有单例模式的基石,强制依赖 getINSTANCE() 方法获取对象。

​​3. 双重检查锁的 getINSTANCE()​​

public static SingletonDemo getINSTANCE() {
    if (INSTANCE == null) {                // 第一次检查(无锁)
        synchronized (SingletonDemo.class) {  // 加锁(确保只有一个线程进入)
            if (INSTANCE == null) {        // 第二次检查(有锁)
                INSTANCE = new SingletonDemo();
            }
        }
    }
    return INSTANCE;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
第一次检查(外层 if)
  • ​​作用​​:避免每次调用 getINSTANCE() 都进入同步块,降低锁竞争开销。
  • ​​原理​​:如果实例已存在(INSTANCE != null),直接返回实例,无需加锁。
同步块(synchronized)​​
  • ​​锁对象​​:使用 SingletonDemo.class 类对象作为锁。
  • ​​目的​​:确保同一时刻只有一个线程能进入临界区执行实例创建。
​​第二次检查(内层 if)​​
  • ​​防止多次实例化​​:多个线程可能同时通过外层检查在等待锁,第一个线程创建实例后,后续线程需再次检查INSTANCE 是否仍为 null,避免重复创建。

​​4. 为什么需要双重检查?​​

  • 单次检查的问题​​:
    • 如果仅在外层检查:无法应对多线程同时首次调用的情况,可能导致多个线程都通过 if (INSTANCE == null) 检查,最终重复创建实例。
    • 如果仅在内层检查:每次调用都需要加锁,即使实例已存在,增加性能开销。设计模式之创建型:单例模式案例:双重检查锁_双重检查锁

​​5. 指令重排序与 volatile 的必要性​​

假设无 volatile 修饰:

INSTANCE = new SingletonDemo();
  • 1.

这行代码可能被 JVM 优化为:

  1. 分配内存空间。
  2. 将 INSTANCE 引用指向内存地址(此时 INSTANCE != null)。
  3. 初始化对象(构造函数执行)。
    如果线程 A 执行到步骤 2(INSTANCE 已为非 null),但未执行步骤 3,此时线程 B 调用 getINSTANCE():

外层检查 INSTANCE != null,直接返回未初始化完成的实例 → ​​程序错误、空指针异常等​​。
volatile 通过禁止指令重排序(通过内存屏障)确保 new SingletonDemo() 原子性:

  1. 分配内存空间。
  2. ​​初始化对象​​。
  3. ​​将 INSTANCE 指向对象地址​​。