面试必会:手写单例模式(从零到完美的五种实现)
在设计模式中,单例模式(Singleton Pattern) 是最简单也是最常考的一种。它的核心思想是:确保一个类只有一个实例,并提供一个全局访问点。
在 Java 后端开发的面试中,面试官常常会要求你“手写一个单例”,并在此基础上追问线程安全、指令重排等问题。本文带你逐一击破。
1. 饿汉式(线程安全,简单直接)
“饿汉”的意思是:类加载时就完成了初始化,不管你用不用,先创建出来再说。
public class Singleton {
// 1. 私有化构造方法,防止外部 new
private Singleton() {}
// 2. 内部创建静态实例
private static final Singleton INSTANCE = new Singleton();
// 3. 提供静态获取方法
public static Singleton getInstance() {
return INSTANCE;
}
}
- 优点:写法简单,类加载时完成实例化,天然线程安全(由 JVM 保证)。
- 缺点:没有达到懒加载(Lazy Loading)的效果。如果该实例很大且一直没被使用,会造成内存浪费。
2. 懒汉式(线程不安全 -> 线程安全)
“懒汉”指的是:只有在第一次调用 getInstance 时才会去创建实例。
线程不安全版
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 多线程环境下,可能多个线程同时进入这个判断
instance = new Singleton();
}
return instance;
}
}
问题:多线程并发时,可能会创建出多个实例,违背单例初衷。
同步方法版(性能低)
public static synchronized Singleton getInstance() { // 加锁
if (instance == null) {
instance = new Singleton();
}
return instance;
}
问题:虽然解决了线程安全,但每次调用都要加锁,性能开销极大。
双重检查锁定(DCL,面试最常考)
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 这一行代码在 JVM 中其实分为三步:
由于 JVM 的指令重排,步骤 2 和 3 的顺序可能会颠倒。如果线程 A 先执行了 1 和 3,此时 instance 已不为 null,但对象初始化还没完成。此时线程 B 进来,第一重检查发现不为 null,直接返回了这个“半成品”对象,程序就会报错。
volatile 的作用就是禁止指令重排,确保对象创建的有序性。
4. 静态内部类(推荐方案)
这是一种更优雅的写法,结合了饿汉式的线程安全和懒汉式的懒加载优点。
public class Singleton {
private Singleton() {};
// 静态内部类只有在被调用时才会被加载
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
原理:只有当调用 getInstance 方法时,才会装在内部类 SingletonHolder,从而触发 INSTANCE 的实例化,由于类加载过程是线程安全的,因此这种方法既实现了延迟加载,又保证了线程安全,代码还非常简介。
枚举单例(最完美的方案)
《Effective Java》作者 Josh Bloch 极力推荐的实现方式。
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("Doing something…");
}
}
调用方式:Singleton.INSTANCE.doSomething;
为什么最好?
总结:我该选哪种?
| 饿汉式 | ❌ | ✅ | ❌ | ⭐⭐ |
| DCL (双重检查) | ✅ | ✅ | ❌ | ⭐⭐⭐⭐ |
| 静态内部类 | ✅ | ✅ | ❌ | ⭐⭐⭐⭐⭐ |
| 枚举 | ❌ | ✅ | ✅ | ⭐⭐⭐⭐⭐ |
面试建议:
- 如果面试官让你写一个“完美的”单例,直接写 静态内部类 或 DCL(记得解释 volatile)。
- 如果面试官问“哪种方式能防御反射攻击”,一定要提到 枚举。
如果你觉得这篇文章对你有帮助,欢迎点赞收藏!你的支持是我创作的最大动力。
网硕互联帮助中心



评论前必须登录!
注册