懒加载单例模式中DCL方式和原理解析

一、DCL 是什么?

DCL(Double Check Lock,双重检查锁)是 Java 中懒加载单例模式的高性能实现方案,核心思路是:

  • 第一次检查:无锁判断实例是否已初始化,避免每次调用都加锁(提升性能);
  • 加锁:保证多线程下只有一个线程能进入初始化逻辑;
  • 第二次检查:防止多个线程等待锁后重复初始化实例。

它解决了传统 "懒汉式(同步方法)" 每次调用都加锁的性能问题,同时保证线程安全。

二、DCL 完整实现代码(标准写法)

java 复制代码
public class SingletonDCL {
    // 1. 必须加 volatile 关键字!
    private static volatile SingletonDCL INSTANCE;

    // 2. 私有构造器,禁止外部实例化
    private SingletonDCL() {}

    // 3. 双重检查锁的核心方法
    public static SingletonDCL getInstance() {
        // 第一次检查:无锁,快速判断实例是否已存在
        // 若已存在,直接返回,无需加锁,提升高并发性能
        if (INSTANCE == null) {
            // 加锁:保证同一时间只有一个线程能进入初始化逻辑
            synchronized (SingletonDCL.class) {
                // 第二次检查:防止多个线程等待锁后重复创建实例
                if (INSTANCE == null) {
                    // 初始化实例
                    INSTANCE = new SingletonDCL();
                }
            }
        }
        return INSTANCE;
    }
}

三、DCL 核心逻辑拆解(为什么要 "双重检查")

假设高并发场景下有 3 个线程(T1、T2、T3)同时调用 getInstance():

  1. T1 先执行 :第一次检查 INSTANCE == null 为 true,进入加锁逻辑,第二次检查仍为 true,执行 INSTANCE = new SingletonDCL(),初始化完成后释放锁;
  2. T2 随后执行 :第一次检查 INSTANCE 已不为 null,直接返回实例,无需加锁;
  3. T3 与 T1 同时执行 :T3 先通过第一次检查(此时 T1 还未完成初始化),等待 T1 释放锁后进入加锁逻辑,第二次检查发现 INSTANCE 已被 T1 初始化,直接返回,避免重复创建。

如果去掉 "第二次检查",T3 会在 T1 释放锁后重新创建实例,导致单例失效。

四、为什么必须加 volatile?(面试必考)

这是 DCL 最核心的坑点!INSTANCE = new SingletonDCL() 看似一行代码,实际 JVM 会拆分为 3 步执行:

java 复制代码
1. 分配内存空间(给 SingletonDCL 实例);
2. 初始化实例(执行构造器逻辑,给成员变量赋值);
3. 将 INSTANCE 引用指向分配的内存空间(此时 INSTANCE 不再为 null)。

JVM 为了优化性能,可能会对这 3 步进行指令重排(比如重排为 1→3→2),导致问题:

  • T1 执行时,JVM 先执行 1→3(INSTANCE 不为 null,但实例还未初始化);
  • T2 此时第一次检查 INSTANCE != null,直接返回这个 "半初始化" 的实例;
  • T2 调用实例的方法时,会因实例未初始化完成抛出空指针或逻辑错误。

volatile 关键字的核心作用:禁止 JVM 对指令重排 ,保证 1→2→3 的执行顺序,确保其他线程看到的 INSTANCE 要么是 null,要么是完全初始化的实例。

五、DCL 的常见误区(避坑)

错误写法 问题说明
去掉 volatile 可能拿到 "半初始化" 实例,线程安全失效
去掉第二次检查 多线程等待锁后重复创建实例,单例失效
同步代码块锁对象错误(比如锁 this) static 方法中 this 不存在,且锁对象不唯一,线程安全失效
把 INSTANCE 定义为 final final 变量必须初始化,无法实现懒加载

六、DCL 的优缺点

优点 缺点
1. 懒加载:实例仅在第一次调用时初始化,节省内存; 2. 高性能:仅初始化时加锁,后续调用无锁; 3. 线程安全:双重检查 + volatile 保证单例唯一性 1. 实现稍复杂,新手易遗漏 volatile; 2. 无法解决 "反射 / 序列化破坏单例" 的问题(需额外处理); 3. 在早期 JDK(1.4 及之前)中,volatile 实现有缺陷,DCL 可能失效(现代 JDK 已修复)

七、DCL 的适用场景

  • 高并发场景下的懒加载单例(比如工具类、连接池、配置中心实例);
  • 对内存占用敏感,需要延迟初始化的场景;
  • 追求高性能,不希望每次调用都加锁的场景。

总结

  1. DCL 是 "懒加载 + 高性能 + 线程安全" 的单例实现方案,核心是 "两次检查 + 加锁 + volatile";
  2. volatile 是 DCL 的关键,用于禁止指令重排,避免拿到半初始化实例;
  3. 第二次检查不可省略,否则多线程下会重复创建实例;
  4. 现代 JDK(1.5+)中 DCL 是安全的,是生产环境中最常用的单例实现方式之一。
相关推荐
wangbing11256 分钟前
springboot应用关闭时触发动作
java·spring boot·后端
weixin_460443563 小时前
2026年新修订《网络安全法》实施:Java私有化考试系统如何做好SBOM、漏洞治理与安全升级?
java·开发语言·在线考试系统·企业培训考试系统·宏远培训考试系统·国企培训考试系统·国产化培训系统
海鸥-w6 小时前
什么是AOP
java
Cc.Y7 小时前
Java零基础入门:可变字符串与包装类:StringBuilder、StringBuffer 与 通讯录管理系统实战
java·开发语言·python
paopaokaka_luck7 小时前
基于springboot+vue的智慧家政管理系统(AI问答、 WebSocket实时沟通、ECharts运营分析、订单规则与服务追踪)
java·javascript·spring boot·数据分析·echarts·mybatis
sm_926787057 小时前
RFID 标签打印的技术实现要点与二次开发实践
java·大数据·前端·c++·编辑器
阿森_dev7 小时前
2027 届毕设选题怎么选:30 个 Java 方向选题的实现难度排序
java·开发语言·spring boot·毕业设计·课程设计
by209997 小时前
C++ 多态:同一个基类接口,调用不同的函数
开发语言·c++·经验分享·多态
笔墨登场说说7 小时前
flink bin/start-cluster.sh 帮我做成开机启动
开发语言·python
Sarvartha7 小时前
Lambda 表达式和函数式接口
java·开发语言