目标:吃透单例模式的本质、常见实现方式、并发安全问题和面试高频问法。
单例模式:确保一个类在 JVM 中只有一个实例,并提供一个全局访问点。
你这个问题非常关键:很多对象确实可以直接 new,而且应该直接 new。
只有当对象满足“全局唯一”约束时,单例才有价值。否则强行单例只会增加耦合。
直接 new 更好的场景:
Order、UserSession)单例更有价值的场景:
一句话判断:
如果“创建多个实例”不会出错,也不会浪费明显资源,就优先直接
new。
单例适合“全局唯一、重复创建成本高、需要统一状态/入口”的对象,例如:
不适合场景:
“饿汉”指的是类一加载就把实例创建好,不管后续有没有用到这个实例。
public class HungrySingleton {
private static final HungrySingleton INSTANCE = new HungrySingleton();
private HungrySingleton() {}
public static HungrySingleton getInstance() {
return INSTANCE;
}
}特点:
“懒汉”指的是第一次真正使用时才创建实例,属于延迟加载思路。
public class LazySingletonUnsafe {
private static LazySingletonUnsafe instance;
private LazySingletonUnsafe() {}
public static LazySingletonUnsafe getInstance() {
if (instance == null) {
instance = new LazySingletonUnsafe();
}
return instance;
}
}为什么线程不安全:
假设线程 A 和线程 B 同时第一次调用 getInstance():
instance == null,结果为 trueinstance == null,结果也为 truenew LazySingletonUnsafe()结论:这种写法只适合单线程环境,在多线程环境下不可靠。
既然问题出在“多个线程同时进入创建逻辑”,最直接的修复方式就是加锁,让同一时刻只有一个线程能进入方法。
public class LazySingletonSync {
private static LazySingletonSync instance;
private LazySingletonSync() {}
public static synchronized LazySingletonSync getInstance() {
if (instance == null) {
instance = new LazySingletonSync();
}
return instance;
}
}特点:线程安全,但每次获取实例都加锁,性能较低。
为什么要 DCL:
synchronized 方法虽然安全,但每次调用都加锁,开销大DCL(Double-Checked Locking)思路:
null 才真正 new 对象public class DclSingleton {
private static volatile DclSingleton instance;
private DclSingleton() {}
public static DclSingleton getInstance() {
if (instance == null) {
synchronized (DclSingleton.class) {
if (instance == null) {
instance = new DclSingleton();
}
}
}
return instance;
}
}关键点:
volatile,避免指令重排导致“拿到未完全初始化对象”。为什么 volatile 必须有:
对象创建不是“一个原子动作”,可能被拆成三步:
instance如果发生指令重排,可能变成 1 -> 3 -> 2。
这时另一个线程读到 instance != null,但对象其实还没初始化完成,就会出现隐患。volatile 可以禁止这类重排,保证可见性与有序性。
这个写法的核心思想是:把实例创建动作放到内部类 Holder 里,Java 的类加载是按需触发(懒加载),不是一次把所有类都加载只有在第一次调用 getInstance() 时,Holder 才会被加载。
public class InnerClassSingleton {
private InnerClassSingleton() {}
private static class Holder {
private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
}
public static InnerClassSingleton getInstance() {
return Holder.INSTANCE;
}
}优势可以理解为三点:
synchronized。所以在工程里,它常被当作“实现成本低、综合表现好”的单例写法。
public enum EnumSingleton {
INSTANCE;
public void doSomething() {
System.out.println("Enum singleton working...");
}
}特点:天然防反射、防反序列化破坏,写法最简洁。
静态内部类 或 枚举单例枚举单例Runtime.getRuntime():典型单例入口Desktop.getDesktop():全局访问实例singletonflowchart LR
A["应用获取 Bean"] --> B["BeanFactory"]
B --> C{"单例池是否存在"}
C -- "是" --> D["直接返回同一实例"]
C -- "否" --> E["创建 Bean 并放入单例池"]
E --> D
回答框架:
静态内部类,强安全选 枚举回答关键词:
破坏方式:
防御方式:
readResolve()(序列化场景)区别要点:
getInstance()”,而在于并发安全和生命周期管理。