首次来源:明略科技 2021 一面
简单介绍下单例模式
最后更新:2026-10-30 · 基于第 1 次作答
结论:单例核心是"全局唯一 + 懒加载权衡",推荐静态内部类 Holder 或枚举。 关键词:饿汉 · 懒汉 · 双检锁 · Holder · 枚举 边界:Spring 的单例是容器管理的 scope,不是手写单例,别混淆。
核心考点定位:单例题面试官看三件事——①几种写法的线程安全性 ②双重校验锁为什么需要 volatile ③反射/序列化如何破坏单例及防御。
机制链(≥3 环):
证据锚点:
volatile:双检锁中禁止 new 的"分配→初始化→引用赋值"重排序,防止拿到未初始化对象。双重检查:外层判 null 避免进入锁,内层判 null 防止重复创建。枚举:JVM 保证枚举单实例,天然防御反射和序列化,这是 Effective Java 推荐的简洁方案。getInstance:所有写法统一对外暴露静态方法获取唯一实例,构造器一律 private。类加载:静态内部类 Holder 由 JVM 类加载机制保证初始化线程安全,无锁懒加载。readResolve:普通单例重写此方法可防御序列化反序列化产生第二个实例。对比与权衡:
| 写法 | 懒加载 | 线程安全 | 防御反射/序列化 | 评价 |
|---|---|---|---|---|
| 饿汉 | ❌ | ✅ | ❌ | 简单,但可能浪费 |
| 懒汉 synchronized | ✅ | ✅ | ❌ | 每次调用加锁,性能差 |
| 双检锁 | ✅ | ✅ | ❌ | 需 volatile,经典 |
| 静态内部类 | ✅ | ✅ | ❌ | 推荐,无锁懒加载 |
| 枚举 | ✅ | ✅ | ✅ | 最简洁,官方推荐 |
常见误区:
setAccessible(true) 能拿到单例 → 正确:枚举能防,普通类构造器加标志位防御 → 为什么:这是单例被击穿的常见攻击点。适用边界:单例不解决"多次 new 的普通对象",它保证的是类级唯一;Spring 里 @Bean 默认单例但由容器管理生命周期,和手写单例不是一个层面。
new Singleton() 的指令重排(分配内存→初始化→赋引用),否则另一线程可能拿到半初始化对象。【结论】 面试官您好,单例模式核心是保证一个类全局只有一个实例,关键在"懒加载"和"线程安全"这两个维度的权衡。
【三点】 第一,饿汉式最简单,类加载就创建实例,天然线程安全,但可能提前占用资源;如果对象创建成本不高、系统启动就要用,直接用饿汉就行,不必炫技。第二,懒汉式延迟到首次访问才创建,但要处理并发——最直接的是加 synchronized,但每次调用都加锁开销大;所以用双重校验锁,先用 volatile 修饰实例变量禁止指令重排,外面判一次 null 避免绝大多数线程进锁,锁内再判一次 null 防止重复创建,两层检查配合才能既懒加载又线程安全。第三,更优雅的是静态内部类 Holder——类加载时不会初始化内部类,首次调用 getInstance 才触发 Holder 初始化,由 JVM 类加载机制保证线程安全,无锁且懒加载,是权衡最优解;如果想最简洁,用枚举,JVM 保证枚举单实例,还天然防御反射和序列化攻击,Effective Java 也推荐。实际开发中我们经常直接饿汉或 Holder,除非对象创建特别昂贵才考虑懒汉。
【边界】 当然,单例不是万能——它保证的是类级唯一,解决不了"多次 new"的普通对象问题;而且 Spring 里 @Bean 默认单例是容器管理生命周期,和手写单例不是一个层面,别把两者混淆。另外如果单例持有大量内存或有状态,全局唯一反而可能成为性能瓶颈,这时候要评估是否需要换成多例。
题目:简单介绍下单例模式
难度:low
标签:real-interview, java/basics, company/明略科技
评分:⭐⭐⭐⭐☆(4/5)
单例模式,核心就是这个对象只创建一次。常规的就是懒汉跟饿汉。其实用的话,直接用饿汉就可以了,直接创建出来后续用,不用考虑那么多,非得炫技的做懒汉式,访问的时候才创建。饿汉式就很好,然后非要用懒汉式的话,访问时候才创建,考虑的多了,然后可以用,你需要考虑并发,考虑一系列事情,然后做的话需要考虑可见性、重排,没有意义,直接开发,直接懒汉式就完事了,这个对象创建,毕竟你创建一个对象对整个系统没那么大影响。然后非要写懒汉式的话,就是首先你要有一个,把它的那个对象设成内存可见性的,Volatile 去标识。然后加锁,创建的时候加锁,而且双层锁。因为这块考虑的东西,就是要考虑它内部的各种竞争机制了。加锁之后确保它不重排,双层验证,最外侧先验证是 null,防止有已经创建的进来。然后内侧加锁,锁的内侧再加一层 null,防止指令重排导致进来。然后在非 null 之内,再去创建这个对象。能创建,但是比较费劲。还有就是直接用枚举做对象创建,枚举类它只初始化一次,也就是个单例,直接就出来了。
优点:饿汉/懒汉对比到位;双重校验锁完整(volatile 可见性 + 外层判 null + 锁内再判 null + 防指令重排);枚举单例也提到了,覆盖面好。
不足:漏了静态内部类 Holder(懒加载 + 线程安全 + 无锁的最优解);没提反射/序列化如何破坏单例及防御。
补充:Holder 是面试高频最优解,枚举防御反射/序列化是加分点。
完整讲解见上方「完整讲解(权威版)」