手写单例模式

168 阅读3分钟

单例模式

手写代码题要求你实现一个单例模式。这里重点强调的是,通常面试中要求实现的单例模式不是“饿汉模式”,而是“懒汉模式”,并且常常要求使用**双重检查锁(DCL)**来实现。

思路

首先,我们来梳理一下双重检查锁(DCL)的实现思路:

  1. 使用 volatile 修饰单例对象
    为了保证单例对象的可见性和禁止指令重排序,我们需要使用 volatile 关键字修饰单例对象。这确保了在多线程环境下,当一个线程创建完单例对象并修改了对象引用后,其他线程能够立即看到这一修改。
  2. 双重检查锁(Double-Checked Locking)
    我们会在获取单例对象时,使用双重检查锁的方式来确保线程安全。在第一次检查时,如果单例对象已经创建,则直接返回;如果没有创建,则进入同步块。在同步块内部,我们再进行一次检查,以确保多个线程不会重复创建对象。

代码实现

以下是懒汉模式(使用双重检查锁)的单例模式Java代码实现:

public class Singleton {

    // volatile关键字保证对象的可见性和禁止指令重排序
    private static volatile Singleton instance;

    // 私有构造函数,防止外部直接创建实例
    private Singleton() {
        // 防止反射创建新的实例
        if (instance != null) {
            throw new IllegalStateException("Cannot create instance, use getInstance()");
        }
    }

    // 获取实例方法
    public static Singleton getInstance() {
        // 第一次检查:如果实例已创建,直接返回
        if (instance == null) {
            synchronized (Singleton.class) {
                // 第二次检查:进入同步块后,仍需再次检查
                if (instance == null) {
                    // 创建实例
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

解释

  1. volatile 修饰实例变量
    volatile 确保在多线程环境下,instance 的修改对所有线程可见,防止因指令重排序导致的问题。
  2. 第一次检查 instance == null
    在同步块外部,首先检查 instance 是否已创建。如果已经创建,直接返回实例,避免每次获取实例时都进入同步块。
  3. 同步块内的第二次检查
    进入同步块后,我们再次检查 instance == null,以防止多个线程同时进入同步块并创建多个实例。这是“双重检查”的核心思想。
  4. 私有构造函数
    私有构造函数保证了外部无法直接创建实例,所有实例的创建都必须通过 getInstance() 方法。
  5. 防止反射
    通过在构造函数中添加检查(如果 instance 已经被创建,则抛出异常),避免通过反射创建多个实例。

小结

通过上述方式,我们实现了一个线程安全的单例模式,同时避免了不必要的同步开销,确保了高效性。双重检查锁(DCL)是一种常见的优化手段,尤其适用于需要延迟加载且只有一个实例的场景。