编写一个线程安全的单例模式

7次阅读
没有评论

 

实现线程安全的单例模式,面试官通常考察的是你对 指令重排序、原子性、以及 JVM 加载机制 的理解。

最推荐的两种写法是 “双重检查锁(DCL)”“静态内部类”


1. 双重检查锁 (Double-Check Locking, DCL)

这是面试中最常写的版本,因为它能体现你对 volatile 和锁性能优化的理解。

Java

public class Singleton {
    // 必须加 volatile,防止指令重排序
    private static volatile Singleton instance;

    private Singleton() {} // 私有构造防止外部实例化

    public static Singleton getInstance() {
        if (instance == null) { // 第一次检查:避免不必要的锁竞争
            synchronized (Singleton.class) {
                if (instance == null) { // 第二次检查:确定真的没被创建
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

面试官必问:为什么要加 volatile

  • 关键点: instance = new Singleton(); 并不是一个原子操作。它分为三步:

    1. 分配内存空间。

    2. 初始化对象。

    3. instance 指向分配的地址。

  • 如果没有 volatile,CPU 可能会为了优化执行 1 -> 3 -> 2

  • 如果线程 A 执行到 3 但还没执行 2,线程 B 进来判断 instance != null,直接拿走了一个还没初始化的半成品对象,导致空指针异常。


2. 静态内部类 (Static Inner Class) —— 最推荐

这种方式既实现了懒加载(Lazy Loading),又保证了绝对的线程安全,代码还简洁。

Java

public class Singleton {
    private Singleton() {}

    // 静态内部类
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

原理:

  • 线程安全: 虚拟机会保证一个类的 <clinit>() 方法在多线程环境中被正确地加锁、同步。

  • 懒加载: 只有在调用 getInstance() 时,才会加载 SingletonHolder 类,此时才实例化对象,不占用多余内存。


3. 枚举单例 (Enum) —— 最安全

如果你想给面试官一个“标准答案之外”的惊喜,可以提这个。

Java

public enum Singleton {
    INSTANCE;
    public void doSomething() { }
}

优点:

  • 防反射/防序列化: 前面两种方法都可以通过反射暴力破解(拿到私有构造函数),或者通过序列化破坏单例。枚举是 JVM 底层保证的,唯一能防止这些手段的单例实现。


总结建议

  • 口播面试: 先说双重检查锁,然后主动解释 volatile 的作用(显得你懂底层)。

  • 笔试/写代码:静态内部类版本,显得你代码风格稳健且追求优雅。

  • 炫技点: 提一句“如果涉及反序列化,枚举单例是最安全的”。

正文完
 0
bdspAdmin
版权声明:本站原创文章,由 bdspAdmin 于2026-04-08发表,共计1310字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)