← 每日面试题 学习看板

简单介绍下单例模式

面试题/真实面试/2021/简单介绍下单例模式.md

简单介绍下单例模式

首次来源:明略科技 2021 一面

原题

简单介绍下单例模式

完整讲解(权威版)

最后更新:2026-10-30 · 基于第 1 次作答

L1 · 30 秒速记

结论:单例核心是"全局唯一 + 懒加载权衡",推荐静态内部类 Holder 或枚举。 关键词:饿汉 · 懒汉 · 双检锁 · Holder · 枚举 边界:Spring 的单例是容器管理的 scope,不是手写单例,别混淆。

L2 · 核心讲解

核心考点定位:单例题面试官看三件事——①几种写法的线程安全性 ②双重校验锁为什么需要 volatile ③反射/序列化如何破坏单例及防御。

机制链(≥3 环):

  1. 饿汉式 → 类加载即实例化,线程安全但可能浪费(类加载早于首次使用)。
  2. 懒汉式 → 首次访问才创建;同步方法版加锁重、双检锁用 volatile 禁止指令重排。
  3. 最优解 → 静态内部类 Holder:类加载时不实例化,首次 getInstance 触发初始化,天然线程安全无锁。

证据锚点:

对比与权衡:

写法懒加载线程安全防御反射/序列化评价
饿汉❌✅❌简单,但可能浪费
懒汉 synchronized✅✅❌每次调用加锁,性能差
双检锁✅✅❌需 volatile,经典
静态内部类✅✅❌推荐,无锁懒加载
枚举✅✅✅最简洁,官方推荐

常见误区:

  1. 错误:双检锁里第二层判 null 是多余的 → 正确:内层判 null 才是防重复创建的关键 → 为什么:外层只防多数线程进锁,内层在锁内重新确认。
  2. 错误:认为枚举单例"不能传参"所以不用 → 正确:可通过带参构造或静态工厂传参 → 为什么:需求简单时枚举是首选。
  3. 错误:用反射 setAccessible(true) 能拿到单例 → 正确:枚举能防,普通类构造器加标志位防御 → 为什么:这是单例被击穿的常见攻击点。

适用边界:单例不解决"多次 new 的普通对象",它保证的是类级唯一;Spring 里 @Bean 默认单例但由容器管理生命周期,和手写单例不是一个层面。

延伸追问(≥3)

  1. 双检锁为什么必须 volatile?
  1. 静态内部类为什么线程安全?
  1. 枚举如何防御序列化攻击?

面试者参考回答(400–700 字)

【结论】 面试官您好,单例模式核心是保证一个类全局只有一个实例,关键在"懒加载"和"线程安全"这两个维度的权衡。

【三点】 第一,饿汉式最简单,类加载就创建实例,天然线程安全,但可能提前占用资源;如果对象创建成本不高、系统启动就要用,直接用饿汉就行,不必炫技。第二,懒汉式延迟到首次访问才创建,但要处理并发——最直接的是加 synchronized,但每次调用都加锁开销大;所以用双重校验锁,先用 volatile 修饰实例变量禁止指令重排,外面判一次 null 避免绝大多数线程进锁,锁内再判一次 null 防止重复创建,两层检查配合才能既懒加载又线程安全。第三,更优雅的是静态内部类 Holder——类加载时不会初始化内部类,首次调用 getInstance 才触发 Holder 初始化,由 JVM 类加载机制保证线程安全,无锁且懒加载,是权衡最优解;如果想最简洁,用枚举,JVM 保证枚举单实例,还天然防御反射和序列化攻击,Effective Java 也推荐。实际开发中我们经常直接饿汉或 Holder,除非对象创建特别昂贵才考虑懒汉。

【边界】 当然,单例不是万能——它保证的是类级唯一,解决不了"多次 new"的普通对象问题;而且 Spring 里 @Bean 默认单例是容器管理生命周期,和手写单例不是一个层面,别把两者混淆。另外如果单例持有大量内存或有状态,全局唯一反而可能成为性能瓶颈,这时候要评估是否需要换成多例。

面试记录 2026-10-30(第 1 次)

题目:简单介绍下单例模式

难度:low

标签:real-interview, java/basics, company/明略科技

评分:⭐⭐⭐⭐☆(4/5)

我的回答

单例模式,核心就是这个对象只创建一次。常规的就是懒汉跟饿汉。其实用的话,直接用饿汉就可以了,直接创建出来后续用,不用考虑那么多,非得炫技的做懒汉式,访问的时候才创建。饿汉式就很好,然后非要用懒汉式的话,访问时候才创建,考虑的多了,然后可以用,你需要考虑并发,考虑一系列事情,然后做的话需要考虑可见性、重排,没有意义,直接开发,直接懒汉式就完事了,这个对象创建,毕竟你创建一个对象对整个系统没那么大影响。然后非要写懒汉式的话,就是首先你要有一个,把它的那个对象设成内存可见性的,Volatile 去标识。然后加锁,创建的时候加锁,而且双层锁。因为这块考虑的东西,就是要考虑它内部的各种竞争机制了。加锁之后确保它不重排,双层验证,最外侧先验证是 null,防止有已经创建的进来。然后内侧加锁,锁的内侧再加一层 null,防止指令重排导致进来。然后在非 null 之内,再去创建这个对象。能创建,但是比较费劲。还有就是直接用枚举做对象创建,枚举类它只初始化一次,也就是个单例,直接就出来了。

点评

优点:饿汉/懒汉对比到位;双重校验锁完整(volatile 可见性 + 外层判 null + 锁内再判 null + 防指令重排);枚举单例也提到了,覆盖面好。

不足:漏了静态内部类 Holder(懒加载 + 线程安全 + 无锁的最优解);没提反射/序列化如何破坏单例及防御。

补充:Holder 是面试高频最优解,枚举防御反射/序列化是加分点。

完整讲解见上方「完整讲解(权威版)」