Skip to content

源码:labs/java/runtime-concurrency/singleton-dcl/src/SingletonDcl.java

  • 原始路径:labs/java/runtime-concurrency/singleton-dcl/src/SingletonDcl.java
  • 类型:java
java
/**
 * 双重检查锁(DCL)+ volatile 的懒加载单例。
 *
 * 特点:
 * - 懒加载:第一次 getInstance() 才进入同步块创建
 * - 线程安全:✅ 内层 synchronized + volatile 安全发布
 * - 性能:🚀 仅首次创建时加锁,之后外层 if 直接返回,适合高频 get
 * - 复杂度:中高,易漏 volatile 或写错双重检查,面试高频
 *
 * Java 场景:
 * - ✅ 需要懒加载且构造要传参 / 依赖运行时配置的单例
 * - ✅ 高并发下频繁 getInstance() 的无参单例(也可用静态内部类替代)
 * - 库代码若不能依赖类加载时机,有时显式 DCL 更清晰
 *
 * Android 场景:
 * - ✅ Application 里按配置延迟 init 的管理器(baseUrl、开关等)
 * - ✅ 不能在类加载阶段就 new 的对象(避免过早触达 SDK / Context)
 * - 注意:单例若持 Context,必须用 applicationContext,与是否 DCL 无关
 * - Kotlin 项目更常见 object / Hilt;DCL 多见于 Java 遗留或需显式 init 参数时
 *
 * 为什么一定要 volatile:
 * - 防止 new SingletonDcl() 指令重排:引用先可见、构造未完成 → 半初始化对象
 *
 * 关键点:
 * - 外层 if:已创建则跳过锁
 * - 内层 if:锁内再判空,保证只 new 一次
 * - volatile:禁止上述重排,保证安全发布
 *
 * 局限:
 * - 手写易错;无参普通单例优先考虑静态内部类或 enum
 *
 * 如何运行:
 * - javac -d out src/*.java
 * - java -cp out SingletonDcl
 *
 * 预期输出:
 * - 两次输出同一个对象引用,same instance = true
 *
 * 易错点:
 * - 去掉 volatile 后单线程仍可能“看起来正常”,并发语义已破坏
 */
public class SingletonDcl {
    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;
    }

    public static void main(String[] args) {
        SingletonDcl a = SingletonDcl.getInstance();
        SingletonDcl b = SingletonDcl.getInstance();
        System.out.println("实例 A 内存地址: " + a);
        System.out.println("实例 B 内存地址: " + b);
        System.out.println("是否为同一单例对象 (a == b): " + (a == b));

        /*
         * 静态阅读参考输出:
         * 实例 A 内存地址: SingletonDcl@7ad041f3
         * 实例 B 内存地址: SingletonDcl@7ad041f3
         * 是否为同一单例对象 (a == b): true
         */
    }
}