Skip to content

singleton-dcl

1. 实验目标与工程要点

本实验对比 Java 单例模式的 5 种常见实现方式(错误懒汉、方法同步、DCL + volatile、静态内部类、枚举单例),重点探讨安全发布 (Safe Publication)、懒加载 (Lazy Initialization) 以及指令重排序带来的“半初始化对象”风险。


2. 深入原理与生活浅显比喻

2.1 生活浅显比喻:建房子与钥匙交付

假设你要找装修公司盖一座房子(创建单例对象):

  • 没有 volatile 的 DCL 风险

    1. 装修公司刚划好了地皮(分配了内存空间);
    2. 交付人员就把门钥匙给了你(将非空引用赋值给静态变量 instance);
    3. 但此时房子内部的水电和家具还没装完(构造函数还没初始化完成!);
    4. 你拿着钥匙推门进去用卫生间,直接摔坑里(拿到半初始化对象,抛出 NullPointerException 或数据损坏)。
  • 加了 volatile 的 DCL 防护

    • 硬件通过内存屏障 (Memory Barrier) 约束:必须等房子装修完全完毕(构造函数完成)之后,才允许把门钥匙交给客户(将引用写入主内存)。

2.2 5 种单例实现方式对比矩阵

单例实现方式懒加载线程安全性吞吐性能运行开销核心原理解析与适用场景
SingletonDcl (DCL + volatile)✅ 属于✅ 安全🚀 较高(仅首次加锁)📉 极低双重检查锁:外层 if 避免已创建后的锁开销,内层 if 保障单例,volatile 阻止指令重排序。
EnumSingleton (枚举单例)❌ 否✅ 绝安全极高(无锁)📉 极低JVM 规范保证:天然防止反射破坏私有构造函数,防止反序列化重新创建对象。推荐
InnerClassSingleton (静态内部类)✅ 属于✅ 安全极高(无锁)📉 极低类加载器机制:依赖 JVM 类加载阶段的 <clinit> 互斥锁实现懒加载与线程安全。代码精简。
SynchronizedSingleton (同步方法)✅ 属于✅ 安全🐢 较差(每次都加锁)📈 较高(方法级锁阻塞)方法加锁public synchronized static getInstance()。每次获取都竞争类锁,吞吐极低,工程不推荐。
BadSingleton (非线程安全)✅ 属于❌ 危险极高📉 极低无同步控制:多线程同时进入 if (instance == null) 时会重复创建多个实例,破坏单例契约。

3. Android / 移动端实战应用场景与事故注意事项

3.1 Android 实战场景(选型速查)

需求推荐实现说明
无参、懒加载、日常业务单例静态内部类 InnerClassSingletonAndroid/Java 最常见;不抢过早类加载
无参、极简、防反射/序列化enum EnumSingleton类加载即创建;Kotlin 项目更常 object
构造要参数 / Application 里 initDCL + volatile SingletonDcl显式控制首次创建时机
教学对照、理解加锁代价SynchronizedSingleton能工作,高频 get 不推荐
生产代码BadSingleton仅反例

全局管理类(网络门面、图片协调等):优先 Holder 或 Hilt @Singleton;持 Context 必须用 applicationContext,与单例写法无关。

3.2 常见误配与内存泄漏事故

  1. ⚠️ DCL 遗漏 volatile 关键字
    • 后果:在高并发高吞吐场景下,极小概率导致线程读取到未完成 init 的半成品对象,引发致命 Crash。
  2. ⚠️ Android 单例持有 Context 导致 Activity 内存泄漏
    • 后果:单例的生命周期与 App 进程同长。如果传入 ActivityContext,会导致 Activity 销毁后无法被 GC 回收,造成严重内存泄漏!
    • 正确修法:单例内部必须统一强制使用 context.applicationContext

4. 实验源码与运行验证

关键源码(特点与使用场景)

各文件类注释含完整说明;下表为速查。

源码懒加载线程安全特点摘要Java 适用Android 适用
BadSingleton.java无同步懒汉,单线程表象正常仅反例,禁止生产多线程/协程下同样危险
SynchronizedSingleton.java方法级 synchronized,每次 get 都争锁低并发、教学可用但不如 Holder;高频不推荐
SingletonDcl.java双重检查 + volatile,仅首次加锁懒加载 + 传参 / 高并发 getApplication 按配置 init 的管理器
InnerClassSingleton.javaHolder 类加载初始化,无锁、写法短无参单例首选之一无/仅 Application Context 的全局类
EnumSingleton.javaJVM 保证唯一,防反射/反序列化无参、库代码、Effective Java 推荐简单无 Context 工具;复杂 init 用 Holder/DI

工程默认顺序(无参单例):静态内部类 ≈ enum > DCL(需传参或显式 init 时)> 方法同步 > 错误懒汉。

运行方式

bash
cd labs/java/runtime-concurrency/singleton-dcl
javac -d out src/*.java
java -cp out SingletonDcl

预期控制台运行输出

text
实例 A 内存地址: SingletonDcl@8807e25
实例 B 内存地址: SingletonDcl@8807e25
是否为同一单例对象 (a == b): true

5. 对应知识库文档