知识模块
☕ Java 知识模块
十一、设计模式
单例模式

单例模式

单例模式确保一个类只有一个实例,并提供一个全局访问点。

一、核心思想

┌─────────────────────────────────────┐
│           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: 为什么推荐枚举实现?

  1. 线程安全:JVM 保证枚举实例的唯一性
  2. 防止反射攻击:反射无法创建枚举实例
  3. 防止反序列化:枚举自带序列化机制
  4. 代码简洁:无需手动实现

Q3: 单例模式在 Spring 中的作用?

Spring Bean 默认作用域是 Singleton:

  • Spring 容器中每个 Bean 只有一个实例
  • Spring 的单例是通过容器管理的,不是设计模式
  • Spring 单例是线程不安全的,需要开发者自行处理并发

Q4: volatile 关键字的作用?

  1. 可见性:一个线程修改后,其他线程立即可见
  2. 禁止指令重排序:防止 DCL 中的指令重排问题

Q5: 如何选择单例实现方式?

需要防止反射/反序列化 → 枚举
需要延迟加载 → 静态内部类
简单场景 → 饿汉式

七、最佳实践

1. 选择合适的实现

  • 推荐:枚举或静态内部类
  • 需要延迟加载:静态内部类
  • 需要防攻击:枚举

2. 注意线程安全

  • 多线程环境必须考虑线程安全
  • DCL 必须使用 volatile
  • 优先使用 JDK 保证的线程安全方案

3. 避免滥用

  • 不是所有场景都适合单例
  • 考虑依赖注入框架(Spring)
  • 注意单例的状态管理

更新时间:2026年3月16日