单例模式
单例模式确保一个类只有一个实例,并提供一个全局访问点。
一、核心思想
┌─────────────────────────────────────┐
│ Singleton Class │
│ ┌─────────────────────────────┐ │
│ │ - instance: Singleton │ │
│ │ - Singleton() │ │
│ │ + getInstance(): Singleton│ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
│
↓
全局唯一实例特点:
- 私有构造方法
- 私有静态实例
- 公有静态获取方法
二、实现方式
1. 饿汉式
public class Singleton {
// 类加载时就创建实例
private static final Singleton INSTANCE = new Singleton();
// 私有构造方法
private Singleton() {}
// 公有获取方法
public static Singleton getInstance() {
return INSTANCE;
}
}优点:
- 实现简单
- 线程安全(类加载时初始化)
- 没有锁开销
缺点:
- 类加载时就创建,可能造成资源浪费
- 无法延迟加载
2. 懒汉式
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 getInstanceSafe() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}同步方法的问题:
- 每次获取实例都要加锁
- 性能开销大
3. 双重检查锁(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() 不是原子操作:
1. 分配内存空间
2. 初始化对象
3. instance 指向内存地址
如果没有 volatile,可能发生指令重排序:1 → 3 → 2
导致其他线程拿到未初始化完成的对象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 时才加载内部类)
- 线程安全(JVM 保证类加载的线程安全)
- 无锁开销
- 推荐使用
5. 枚举实现
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("Singleton method");
}
}
// 使用
Singleton.INSTANCE.doSomething();优点:
- 线程安全
- 防止反序列化创建新对象
- 防止反射攻击
- 代码简洁
- Effective Java 推荐方式
三、实现方式对比
| 方式 | 线程安全 | 延迟加载 | 防止反射攻击 | 防止反序列化 | 推荐度 |
|---|---|---|---|---|---|
| 饿汉式 | ✅ | ❌ | ❌ | ❌ | ⭐⭐⭐ |
| 懒汉式(同步) | ✅ | ✅ | ❌ | ❌ | ⭐⭐ |
| 双重检查锁 | ✅ | ✅ | ❌ | ❌ | ⭐⭐⭐⭐ |
| 静态内部类 | ✅ | ✅ | ❌ | ❌ | ⭐⭐⭐⭐⭐ |
| 枚举 | ✅ | ❌ | ✅ | ✅ | ⭐⭐⭐⭐⭐ |
四、破坏单例的方式
1. 反射攻击
// 破坏单例
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton instance1 = constructor.newInstance();
Singleton instance2 = constructor.newInstance();
// instance1 != instance2防御方式:
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
// 防止反射攻击
if (instance != null) {
throw new RuntimeException("单例模式禁止反射创建实例");
}
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}2. 序列化/反序列化
// 序列化破坏单例
Singleton instance1 = Singleton.getInstance();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(instance1);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Singleton instance2 = (Singleton) ois.readObject();
// instance1 != instance2防御方式:
public class Singleton implements Serializable {
private static volatile Singleton instance;
private Singleton() {}
// 防止反序列化破坏单例
private Object readResolve() {
return instance;
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}五、应用场景
1. 配置管理器
public class ConfigManager {
private static final ConfigManager INSTANCE = new ConfigManager();
private Properties config = new Properties();
private ConfigManager() {
// 加载配置
}
public static ConfigManager getInstance() {
return INSTANCE;
}
public String getConfig(String key) {
return config.getProperty(key);
}
}2. 数据库连接池
public class ConnectionPool {
private static volatile ConnectionPool instance;
private List<Connection> connections;
private ConnectionPool() {
// 初始化连接池
}
public static ConnectionPool getInstance() {
if (instance == null) {
synchronized (ConnectionPool.class) {
if (instance == null) {
instance = new ConnectionPool();
}
}
}
return instance;
}
}3. 日志管理器
public class Logger {
private static class LoggerHolder {
private static final Logger INSTANCE = new Logger();
}
private Logger() {}
public static Logger getInstance() {
return LoggerHolder.INSTANCE;
}
public void log(String message) {
System.out.println("[LOG] " + message);
}
}4. 线程池
public class ThreadPoolManager {
private static volatile ThreadPoolManager instance;
private ExecutorService executorService;
private ThreadPoolManager() {
executorService = Executors.newFixedThreadPool(10);
}
public static ThreadPoolManager getInstance() {
if (instance == null) {
synchronized (ThreadPoolManager.class) {
if (instance == null) {
instance = new ThreadPoolManager();
}
}
}
return instance;
}
public void execute(Runnable task) {
executorService.execute(task);
}
}六、面试高频问题
Q1: 单例模式有什么优缺点?
优点:
- 内存中只有一个实例,减少内存开销
- 避免对资源的多重占用
- 全局访问点,方便控制
缺点:
- 没有抽象层,扩展困难
- 单例类职责过重,违背单一职责原则
- 滥用会带来负面问题(如共享状态)
Q2: 为什么推荐枚举实现?
- 线程安全:JVM 保证枚举实例的唯一性
- 防止反射攻击:反射无法创建枚举实例
- 防止反序列化:枚举自带序列化机制
- 代码简洁:无需手动实现
Q3: 单例模式在 Spring 中的作用?
Spring Bean 默认作用域是 Singleton:
- Spring 容器中每个 Bean 只有一个实例
- Spring 的单例是通过容器管理的,不是设计模式
- Spring 单例是线程不安全的,需要开发者自行处理并发
Q4: volatile 关键字的作用?
- 可见性:一个线程修改后,其他线程立即可见
- 禁止指令重排序:防止 DCL 中的指令重排问题
Q5: 如何选择单例实现方式?
需要防止反射/反序列化 → 枚举
需要延迟加载 → 静态内部类
简单场景 → 饿汉式七、最佳实践
1. 选择合适的实现
- 推荐:枚举或静态内部类
- 需要延迟加载:静态内部类
- 需要防攻击:枚举
2. 注意线程安全
- 多线程环境必须考虑线程安全
- DCL 必须使用 volatile
- 优先使用 JDK 保证的线程安全方案
3. 避免滥用
- 不是所有场景都适合单例
- 考虑依赖注入框架(Spring)
- 注意单例的状态管理
更新时间:2026年3月16日