单例模式-3-双检锁/双重校验锁(DCL,即 double-checked locking)

发布于:2025-04-16 ⋅ 阅读:(33) ⋅ 点赞:(0)

以下是对这段代码的详细解释,这是一种实现 双重检查锁定(Double-Checked Locking) 的单例模式:


1. 类的定义

public class Singleton {
    private volatile static Singleton singleton;
    private Singleton() {}
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

2. 代码逐步解析

2.1. private volatile static Singleton singleton;
  • private:确保 singleton 变量只能在 Singleton 类内部访问,防止外部直接修改。
  • static:确保 singleton 是类级别的变量,所有实例共享同一个变量。
  • volatile:保证多线程环境下的可见性和有序性。
    • 可见性:当一个线程修改了 singleton 的值,其他线程能够立即看到修改后的值。
    • 有序性:防止指令重排序问题(即对象未完全初始化时,其他线程可能访问到未初始化的对象)。

2.2. private Singleton() {}
  • 将构造方法声明为 private,防止外部通过 new 关键字创建实例。
  • 这是单例模式的核心,确保只能通过类的静态方法获取实例。

2.3. public static Singleton getSingleton()

这是获取单例实例的公共方法,包含了双重检查锁定的逻辑。


2.4. if (singleton == null)
  • 第一次检查:在进入同步块之前,检查 singleton 是否为 null
    • 如果 singleton 已经被初始化,则直接返回实例,避免不必要的同步开销。
    • 如果 singletonnull,说明实例尚未创建,进入同步块。

2.5. synchronized (Singleton.class)
  • 同步块:对 Singleton.class 加锁,确保只有一个线程能够进入同步块,避免多个线程同时创建实例。
  • 这是双重检查锁定的关键,确保线程安全。

2.6. if (singleton == null)
  • 第二次检查:进入同步块后,再次检查 singleton 是否为 null
    • 这是因为可能有多个线程在第一次检查时通过了 if (singleton == null),但只有一个线程能够进入同步块。
    • 如果没有第二次检查,可能会导致多个线程创建多个实例。

2.7. singleton = new Singleton();
  • 创建单例实例。
  • 由于 volatile 的作用,确保对象初始化完成后,其他线程能够正确看到 singleton 的值。

2.8. return singleton;
  • 返回单例实例。

3. 为什么需要双重检查锁定?

双重检查锁定的目的是在保证线程安全的同时,尽量减少同步的开销:

  1. 第一次检查:避免每次调用 getSingleton() 都进入同步块,提高性能。
  2. 第二次检查:确保在多线程环境下,只有一个线程能够创建实例,保证线程安全。

4. 为什么需要 volatile

在没有 volatile 的情况下,可能会出现指令重排序问题:

  1. singleton = new Singleton(); 不是一个原子操作,实际上分为三步:
    • 分配内存。
    • 调用构造函数初始化对象。
    • 将对象引用赋值给 singleton
  2. 如果发生指令重排序,可能会导致以下情况:
    • 线程 A 执行了第 1 步和第 3 步,但尚未完成第 2 步。
    • 线程 B 访问到未完全初始化的对象,可能导致程序崩溃。
  3. 使用 volatile 可以禁止指令重排序,确保对象初始化的有序性。

5. 优点

  • 线程安全:通过双重检查和同步块,确保多线程环境下的安全性。
  • 性能优化:避免每次获取实例时都加锁,提升性能。
  • 延迟加载:实例在第一次使用时才会被创建,节省内存。

6. 缺点

  • 实现较复杂,容易出错。
  • 在某些情况下,可能会增加代码的维护成本。

7. 总结

这段代码是经典的双重检查锁定单例模式的实现,适用于多线程环境下的单例创建。通过 volatile 和双重检查机制,既保证了线程安全,又优化了性能,是一种高效的单例模式实现方式。


网站公告

今日签到

点亮在社区的每一天
去签到