概述
单例模式(Singleton Pattern)是 Java 中最常用、最基础的创建型设计模式之一。
它的核心目标是:确保一个类在整个应用程序生命周期中只有一个实例,并提供一个全局访问点。
适用于:
1、需要频繁创建和销毁的对象;
2、创建消耗资源太多有会频繁创建的对象;
3、有状态的工具类对象;
4、频繁访问数据库或文件的对象。
在 Java 中,这意味着:
该类不能被外部随意 new;
整个 JVM 中只存在一个该类的对象;
所有需要使用该对象的地方都通过同一个入口获取。
典型场景
| 场景 | 说明 |
|---|---|
| 配置管理器 | 应用启动时加载一次配置,后续所有模块共享同一份配置 |
| 日志记录器 | 避免多个 Logger 实例导致日志混乱或文件锁冲突 |
| 数据库连接池 | 连接池本身应是全局唯一的资源管理中心 |
| 缓存中心 | 如内存缓存(如本地 Map 缓存),需统一管理 |
| 计数器/ID 生成器 | 需保证全局唯一性 |
核心要素
私有构造方法:防止外部通过 new 创建实例。
持有自身静态实例:通常用 private static 修饰。
提供公共静态方法获取实例:如 getInstance()。
常见实现方式
1. 饿汉式(Eager Initialization)
java
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
优点:线程安全、实现简单
缺点:类加载时就创建实例,可能浪费资源(即使没用到)
2. 懒汉式(Lazy Initialization)--- 非线程安全
java
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
多线程下可能创建多个实例,不推荐使用。
3. 懒汉式 + 同步方法(线程安全但低效)
java
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
线程安全
每次调用都要同步,性能差
4. 双重检查锁定(Double-Checked Locking) 推荐
java
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,防止 JVM 指令重排导致未初始化完成的对象被引用。
关键点:volatile 的作用
instance = new DCLSingleton(); 实际分三步:
- 分配内存空间
- 调用构造方法,初始化对象
- 将
instance指向分配的内存地址
JVM 可能重排序为:1 → 3 → 2(指令重排优化)
如果没有 volatile:
- 线程 A 执行到第 3 步(
instance != null),但对象未初始化完成 - 线程 B 调用
getInstance(),看到instance != null,直接返回 - B 使用了一个未完全构造的对象 → 空指针或状态错误!
volatile 禁止指令重排序 + 保证可见性
5. 静态内部类(推荐)
java
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
线程安全(由 JVM 类加载机制保证)
延迟加载(只有调用 getInstance() 时才加载内部类)
代码简洁、无锁、性能高
强烈推荐用于大多数场景
6. 枚举方式(最安全)
java
public enum Singleton {
INSTANCE;
public void doSomething() {
// 业务方法
}
}
天然线程安全
防止反射攻击
防止反序列化破坏单例(枚举的序列化机制特殊)
《Effective Java》作者 Joshua Bloch 强烈推荐
使用方式:Singleton.INSTANCE.doSomething();
为什么最安全
| 攻击方式 | 普通单例 | 枚举单例 |
|---|---|---|
| 反射 | 可通过 setAccessible(true) 调用私有构造器 |
枚举构造器不能被反射调用(JVM 层面禁止) |
| 反序列化 | 若未实现 readResolve(),会创建新对象 |
枚举的序列化机制特殊,反序列化返回原实例 |
| 多实例 | 可能因上述原因被破坏 | 绝对保证单例 |
缺点:无法继承(枚举隐式继承 Enum);不能懒加载(其实枚举实例也是类加载时初始化,类似饿汉式);部分框架(如某些老 ORM)对枚举支持不好。
7、性能对比
| 实现方式 | 线程安全 | 延迟加载 | 性能 | 安全性 |
|---|---|---|---|---|
| 饿汉式 | ✅ | ❌ | 高 | 中 |
| 懒汉式(无锁) | ❌ | ✅ | 高 | 低 |
| synchronized 方法 | ✅ | ✅ | 低 | 中 |
| DCL(volatile) | ✅ | ✅ | 高 | 高(需正确实现) |
| 静态内部类 | ✅ | ✅ | 高 | 高 |
| 枚举 | ✅ | ❌(类加载时) | 高 | 最高 |
📌 实际性能差异在现代 JVM 上极小,安全性 > 微优化
注意事项
| 问题 | 说明 |
|---|---|
| 反射攻击 | 通过反射可调用私有构造器,破坏单例(枚举可防御) |
| 序列化/反序列化 | 反序列化可能创建新对象,需实现 readResolve() 方法: private Object readResolve() { return INSTANCE; } |
| 多类加载器 | 不同 ClassLoader 可能加载多个单例(一般应用无需考虑) |
常见误区
误区1:单例能解决所有"全局状态"问题
→ 单例本质是全局状态,过度使用会导致:
- 代码耦合度高
- 难以单元测试(需 mock 单例)
- 隐藏依赖(调用方不显式传参)
替代方案:考虑依赖注入(DI),如 Spring 的 @Component + 单例作用域
误区2:单例一定是"好"的
→ 如果对象状态会变,且被多线程修改,仍需同步控制(单例 ≠ 线程安全对象)
误区3:Spring 中的 Bean 默认是单例,所以不用自己写
→ 正确!在 Spring 应用中,通常用 @Service / @Component 即可,容器管理生命周期。
如何选择
| 你的需求 | 推荐实现 |
|---|---|
| 追求极致安全、不怕饿汉式 | 枚举 |
| 需要延迟加载、代码清晰 | 静态内部类 |
| 老项目、必须兼容 JDK 1.4 | DCL(但尽量升级) |
| Spring 项目 | 直接用 @Component,别手写单例 |