核心内容摘要
聆听“铜铜铜铜铜铜铜好多水铜铜铜铜铜铜”的交响曲:一次穿越时空的感官之旅
DCL 是什么DCLDouble Check Lock双重检查锁是 Java 中懒加载单例模式的高性能实现方案核心思路是第一次检查无锁判断实例是否已初始化避免每次调用都加锁提升性能加锁保证多线程下只有一个线程能进入初始化逻辑第二次检查防止多个线程等待锁后重复初始化实例。
它解决了传统 “懒汉式同步方法” 每次调用都加锁的性能问题同时保证线程安全。
DCL 完整实现代码标准写法public class SingletonDCL { //
必须加 volatile 关键字 private static volatile SingletonDCL INSTANCE; //
私有构造器禁止外部实例化 private SingletonDCL() {} //
双重检查锁的核心方法 public static SingletonDCL getInstance() { // 第一次检查无锁快速判断实例是否已存在 // 若已存在直接返回无需加锁提升高并发性能 if (INSTANCE null) { // 加锁保证同一时间只有一个线程能进入初始化逻辑 synchronized (SingletonDCL.class) { // 第二次检查防止多个线程等待锁后重复创建实例 if (INSTANCE null) { // 初始化实例 INSTANCE new SingletonDCL(); } } } return INSTANCE; } }
DCL 核心逻辑拆解为什么要 “双重检查”假设高并发场景下有 3 个线程T
T
T3同时调用getInstance()T1 先执行第一次检查INSTANCE null为 true进入加锁逻辑第二次检查仍为 true执行INSTANCE new SingletonDCL()初始化完成后释放锁T2 随后执行第一次检查INSTANCE已不为 null直接返回实例无需加锁T3 与 T1 同时执行T3 先通过第一次检查此时 T1 还未完成初始化等待 T1 释放锁后进入加锁逻辑第二次检查发现INSTANCE已被 T1 初始化直接返回避免重复创建。
如果去掉 “第二次检查”T3 会在 T1 释放锁后重新创建实例导致单例失效。
为什么必须加volatile面试必考这是 DCL 最核心的坑点INSTANCE new SingletonDCL()看似一行代码实际 JVM 会拆分为 3 步执行
分配内存空间给 SingletonDCL 实例
初始化实例执行构造器逻辑给成员变量赋值
将 INSTANCE 引用指向分配的内存空间此时 INSTANCE 不再为 null。
JVM 为了优化性能可能会对这 3 步进行指令重排比如重排为 1→3→2导致问题T1 执行时JVM 先执行 1→3INSTANCE 不为 null但实例还未初始化T2 此时第一次检查INSTANCE ! null直接返回这个 “半初始化” 的实例T2 调用实例的方法时会因实例未初始化完成抛出空指针或逻辑错误。
volatile关键字的核心作用禁止 JVM 对指令重排保证 1→2→3 的执行顺序确保其他线程看到的INSTANCE要么是 null要么是完全初始化的实例。