找回密码
 立即注册
首页 业界区 安全 Java单例模式:从实战到面试的深度解析

Java单例模式:从实战到面试的深度解析

拼潦 2025-5-31 23:59:01
结论先行


  • 饿汉式:线程安全但可能造成资源浪费,推荐在初始化成本低的场景使用
  • 懒汉式:需要解决线程安全问题,推荐使用双重检查锁+volatile优化
  • 静态内部类:最佳实践方案,完美平衡延迟加载与线程安全
  • 枚举单例:JDK1.5+推荐方案,天然防反射/序列化破坏
  • 实际开发中优先选择枚举或静态内部类实现
文章持续更新,可以微信搜一搜「 半个脑袋儿 」第一时间阅读
一、核心实现方式

1. 饿汉式
  1. class ClassA {
  2.     private static final ClassA INSTANCE = new ClassA();
  3.    
  4.     public static ClassA getInstance() {
  5.         return INSTANCE;
  6.     }
  7.    
  8.     private ClassA() {} // 防止反射创建
  9. }
复制代码
特点

  • 类加载时立即初始化(可能造成资源浪费)
  • 天然线程安全
  • 需处理反射攻击(添加私有构造器判空逻辑)
2. 双重检查锁懒汉式
  1. class ClassB {
  2.     private static volatile ClassB instance;
  3.    
  4.     public static ClassB getInstance() {
  5.         if (instance == null) {                     // 第一次检查
  6.             synchronized (ClassB.class) {            // 同步锁
  7.                 if (instance == null) {             // 第二次检查
  8.                     instance = new ClassB();
  9.                 }
  10.             }
  11.         }
  12.         return instance;
  13.     }
  14.    
  15.     private ClassB() {}
  16. }
复制代码
关键点

  • volatile防止指令重排序(JDK5+的JMM修复)
  • 两次null检查确保性能与线程安全
  • 仍可能被反射破坏单例
3. 静态内部类
  1. class ClassC {
  2.     private static class Holder {
  3.         static final ClassC INSTANCE = new ClassC();
  4.     }
  5.    
  6.     public static ClassC getInstance() {
  7.         return Holder.INSTANCE;
  8.     }
  9.    
  10.     private ClassC() {}
  11. }
复制代码
优势

  • 利用类加载机制保证线程安全
  • 实现延迟加载(调用getInstance时才会初始化)
  • 代码简洁无锁
4. 枚举式
  1. enum EnumSingleton {
  2.     INSTANCE;
  3.    
  4.     public void businessMethod() {
  5.         // 业务方法
  6.     }
  7. }
复制代码
绝对优势

  • 天生防反射攻击(枚举类没有构造器)
  • 自动处理序列化/反序列化
  • 代码极度简洁
二、实战应用场景

1. 配置管理类
  1. public enum ConfigManager {
  2.     INSTANCE;
  3.    
  4.     private Properties props = new Properties();
  5.    
  6.     ConfigManager() {
  7.         try(InputStream is = getClass().getResourceAsStream("/app.properties")) {
  8.             props.load(is);
  9.         }
  10.     }
  11.    
  12.     public String getProperty(String key) {
  13.         return props.getProperty(key);
  14.     }
  15. }
复制代码
2. 数据库连接池
  1. public class ConnectionPool {
  2.     private static final int MAX_SIZE = 100;
  3.     private BlockingQueue<Connection> pool = new ArrayBlockingQueue<>(MAX_SIZE);
  4.    
  5.     private static class Holder {
  6.         static final ConnectionPool INSTANCE = new ConnectionPool();
  7.     }
  8.    
  9.     private ConnectionPool() {
  10.         // 初始化连接池
  11.     }
  12.    
  13.     public static ConnectionPool getInstance() {
  14.         return Holder.INSTANCE;
  15.     }
  16.    
  17.     public Connection getConnection() throws InterruptedException {
  18.         return pool.take();
  19.     }
  20. }
复制代码
3. Spring中的单例


  • Spring默认的Bean作用域就是单例
  • 通过IOC容器管理生命周期
  • 与设计模式单例的区别:每个容器对应一个实例
三、高频面试题解析

Q1:DCL(双重检查锁)为什么要加volatile?

:防止指令重排序导致返回未初始化完成的对象。new操作不是原子操作,分为:

  • 分配内存空间
  • 初始化对象
  • 将引用指向内存地址
不加volatile可能导致步骤2和3重排序,其他线程可能拿到未初始化完成的对象。
Q2:如何防止反射攻击?
  1. private ClassC() {
  2.     if (Holder.INSTANCE != null) {
  3.         throw new RuntimeException("禁止反射创建!");
  4.     }
  5. }
复制代码
Q3:枚举单例如何防止反射?


  • 枚举类的构造方法由JVM特殊处理
  • 反射newInstance方法会直接抛出异常
Q4:单例对象什么时候会被回收?


  • 只有当加载该类的ClassLoader被回收时才会被回收
  • 一般情况(使用系统类加载器)会与JVM生命周期一致
Q5:单例模式的优缺点?

优点

  • 内存中只有一个实例,减少内存开销
  • 避免对资源的多重占用
缺点

  • 违背单一职责原则(既要管理实例又要处理业务)
  • 扩展困难(需要修改源码)
  • 测试困难(全局状态难以隔离)

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册