Java 单例模式完全指南
一、什么是单例模式
单例模式(Singleton Pattern) 是 Java 中最简单也是最常用的设计模式之一。它确保一个类在整个应用运行期间只有一个实例存在,并提供一个访问该实例的全局访问点。
核心要点:
- 私有构造器(Private Constructor):防止外部通过
new关键字创建实例。 - 私有静态实例(Private Static Instance):类内部维护唯一的实例对象。
- 公共静态获取方法(Public Static getInstance):提供全局访问点。
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
二、单例模式的多种实现方式
1. 饿汉式(Eager Initialization)
类加载时就创建实例,简单可靠,天生线程安全。
public class EagerSingleton {
// 类加载时立即初始化
private static final EagerSingleton INSTANCE = new EagerSingleton();
// 私有构造器
private EagerSingleton() {
// 防止反射破坏(可选加强)
if (INSTANCE != null) {
throw new RuntimeException("单例已存在,不允许反射创建");
}
}
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
优点:实现简单,无线程安全问题
缺点:如果实例创建成本高且从未使用,会造成资源浪费
2. 懒汉式(Lazy Initialization)
第一次调用 getInstance() 时才创建实例。
2.1 简单懒汉式(线程不安全)
public class LazySingletonUnsafe {
private static LazySingletonUnsafe instance;
private LazySingletonUnsafe() {}
public static LazySingletonUnsafe getInstance() {
if (instance == null) {
instance = new LazySingletonUnsafe(); // 多线程下可能创建多个实例
}
return instance;
}
}
问题:多个线程可能同时通过 if (instance == null) 检查,从而创建多个实例。
2.2 同步方法懒汉式(线程安全但性能差)
public class LazySingletonSync {
private static LazySingletonSync instance;
private LazySingletonSync() {}
// 整个方法加锁,每次获取实例都要同步
public static synchronized LazySingletonSync getInstance() {
if (instance == null) {
instance = new LazySingletonSync();
}
return instance;
}
}
缺点:即使实例已创建,每次调用仍需竞争锁,高并发下性能低下。
3. 双重检查锁定(Double-Checked Locking)—— 推荐
在同步块前后各检查一次,结合 volatile 关键字保证可见性和禁止指令重排。
public class DCLSingleton {
// volatile 保证可见性,并防止指令重排
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?instance = new DCLSingleton(); 这行代码在 JVM 中并非原子操作,大约分为三步:
- 分配内存空间
- 执行构造函数初始化对象
- 将
instance指向分配的内存地址
JIT 编译器可能对指令进行重排序,变成 1→3→2。此时若另一个线程在第一个检查中发现 instance 不为 null,就直接返回,但获得的是一个尚未初始化完成的对象。volatile 禁止了这种指令重排序。
优点:延迟加载,线程安全,高并发性能好
4. 静态内部类(Holder Class)—— 优雅的懒加载
利用类加载机制保证初始化实例时只有一个线程。既实现了延迟加载,又保证了线程安全,且写法简洁。
public class HolderSingleton {
private HolderSingleton() {}
// 静态内部类在第一次被引用时才会加载
private static class SingletonHolder {
private static final HolderSingleton INSTANCE = new HolderSingleton();
}
public static HolderSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
原理:JVM 保证类的静态变量初始化是线程安全的,而且静态内部类只有在调用 getInstance() 时才会被加载,实现了延迟实例化。
优点:写法简洁,天生线程安全,无性能缺陷
5. 枚举单例(Enum Singleton)—— 最安全的实现
由 Joshua Bloch(Java 著名架构师)在《Effective Java》中推荐。无偿提供了序列化机制,并绝对防止多次实例化(包括反射攻击),是目前最安全的单例实现。
public enum EnumSingleton {
INSTANCE; // 单例实例
// 可以添加方法和字段
private String config;
public String getConfig() {
return config;
}
public void setConfig(String config) {
this.config = config;
}
// 业务方法
public void doSomething() {
System.out.println("枚举单例执行操作");
}
}
// 使用
EnumSingleton.INSTANCE.doSomething();
枚举的优势:
- 天然防反射攻击(反射
newInstance会抛出异常) - 天然防序列化破坏(序列化后反序列化仍是同一实例)
- 写法极简
三、单例模式的“破坏”与防御
即使精心实现的单例,也可能被以下方式破坏:
1. 反射攻击及防御
// 反射强行调用私有构造器
Constructor<DCLSingleton> constructor = DCLSingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
DCLSingleton instance1 = constructor.newInstance();
DCLSingleton instance2 = DCLSingleton.getInstance();
System.out.println(instance1 == instance2); // false,单例被破坏
防御方法:在构造器中添加标志位判断
public class SafeSingleton {
private static volatile boolean flag = false;
private SafeSingleton() {
if (flag) {
throw new RuntimeException("单例已被创建,禁止反射攻击");
}
flag = true;
}
// ... 其余代码
}
注意:若攻击者先修改
flag字段的值,该防御仍可能被绕过。枚举没有此类问题。
2. 序列化破坏及防御
Java 序列化/反序列化会创建新对象,破坏单例。
// 序列化后反序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.obj"));
oos.writeObject(instance);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.obj"));
SafeSingleton newInstance = (SafeSingleton) ois.readObject();
System.out.println(instance == newInstance); // false
防御方法:添加 readResolve() 方法
public class SerializableSingleton implements Serializable {
private static final long serialVersionUID = 1L;
private static final SerializableSingleton INSTANCE = new SerializableSingleton();
private SerializableSingleton() {}
public static SerializableSingleton getInstance() {
return INSTANCE;
}
// 反序列化时自动调用此方法,返回已有实例
protected Object readResolve() {
return INSTANCE;
}
}
枚举单例自带此特性,无需额外处理。
四、单例模式的优缺点与适用场景
优点
- 严格控制实例数量,节约系统资源
- 提供全局访问点,方便统一管理
- 扩展性强(可从单例扩展为多例)
缺点
- 没有抽象层,扩展困难
- 职责过重,可能违反单一职责原则
- 全局状态可能带来隐式耦合
- 在并发场景下如果设计不当会引发问题
适用场景
- 需要频繁创建和销毁的对象(如线程池、数据库连接池)
- 有状态的工具类对象
- 需要控制资源访问的对象(如配置文件管理器、日志管理器)
- Windows 任务管理器、回收站等系统级服务
- Spring 中默认 Bean 作用域(singleton)
五、Spring 框架中的单例
Spring 容器中的 Bean 默认就是单例模式,但它的实现更为灵活:
- 通过
ConcurrentHashMap维护单例缓存池 - 采用三级缓存解决循环依赖
- 作用域可通过
@Scope("prototype")改变 - 底层并非使用传统设计模式,而是基于容器的统一管理
六、总结对比
| 实现方式 | 线程安全 | 懒加载 | 防反射 | 防序列化 | 性能 | 推荐指数 |
|---|---|---|---|---|---|---|
| 饿汉式 | ✅ | ❌ | ❌ | ❌ | 高 | ⭐⭐ |
| 懒汉式(sync) | ✅ | ✅ | ❌ | ❌ | 低 | ⭐ |
| 双重检查锁 | ✅ | ✅ | ❌ | ❌ | 高 | ⭐⭐⭐⭐ |
| 静态内部类 | ✅ | ✅ | ❌ | ❌ | 高 | ⭐⭐⭐⭐ |
| 枚举 | ✅ | ❌(饿汉) | ✅ | ✅ | 高 | ⭐⭐⭐⭐⭐ |
推荐选择策略:
- 日常开发直接用枚举,除非需要懒加载
- 需要懒加载且不需要防御反射/反序列化:双重检查锁或静态内部类
- 框架/容器环境(如 Spring):交给容器管理
七、完整示例
下面是一个综合了防御机制的工业级饿汉式单例:
import java.io.Serializable;
public final class CosmicSingleton implements Serializable {
private static final long serialVersionUID = 42L;
// 饿汉式实例
private static final CosmicSingleton INSTANCE = new CosmicSingleton();
// 反射防御标志
private static final boolean INITIALIZED;
static {
// 类加载时自动执行,确保单例创建
INITIALIZED = true;
}
private CosmicSingleton() {
// 防御反射攻击
if (INITIALIZED) {
throw new RuntimeException("Use getInstance() to access singleton");
}
}
public static CosmicSingleton getInstance() {
return INSTANCE;
}
// 防御序列化破坏
protected Object readResolve() {
return INSTANCE;
}
// 业务方法
public void showMessage() {
System.out.println("Hello, Cosmic Singleton!");
}
}
掌握单例模式的这些实现与变体,你就能在任何 Java 场景中游刃有余地控制对象的唯一性了。
更多推荐


所有评论(0)