Java设计模式之单例模式详解

​“单例模式是设计模式中最简单的形式之一,却也是面试中最常被深挖的考点。”​​ ——《设计模式:可复用面向对软件的基础》

1. 什么是单例模式?

单例模式(Singleton Pattern)是一种​​创建型设计模式​​,它确保一个类​​仅有一个实例​​存在,并提供一个​​全局访问点​​供外部获取该实例。数学逻辑中的singleton定义为“有且仅有一个元素的集合”,在软件设计中则体现为对唯一实例的严格控制。

2. 三大核心特征

  1. ​唯一实例​​:类只能创建一个对象实例
  2. ​自我创建​​:实例由类自身创建并管理
  3. ​全局访问​​:提供统一的静态访问入口

3. 典型应用场景

应用场景实例说明必要性分析
配置管理器统一管理应用配置参数避免配置信息不一致
线程池/连接池数据库连接池、线程池防止资源竞争和超限分配
日志系统全局日志记录器确保日志写入顺序一致性
对话框/弹窗系统级对话框避免重复弹窗干扰用户
设备驱动打印机、显卡驱动访问防止硬件指令冲突

二、单例模式的六大实现方式及原理剖析

1. 饿汉式(线程安全)

public class EagerSingleton {
    // 类加载时立即初始化(线程安全)
    private static final EagerSingleton INSTANCE = new EagerSingleton();
    
    private EagerSingleton() { // 私有构造器阻外部实例化 }
    
    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

特点​​:

  • 启动即创建​​:类加载时静态初始化实例
  • 绝对线程安全​​:由JVM类加载机制保证
  • 资源浪费风险​​:未使用时仍占用内存

2. 懒汉式基础版(线程不安全)

public class UnsafeLazySingleton {
    private static UnsafeLazySingleton instance;
    
    private UnsafeLazySingleton() {}
    
    public static UnsafeLazySingleton getInstance() {
        if (instance == null) {
            instance = new UnsafeLazySingleton(); // 多线程下可能创建多个实例
        }
        return instance;
    }
}

致命缺陷​​:​​并发创建漏洞​​——多个线程同时通过null检查时,会实例化多个对象

3. 同步锁懒汉式(线程安全但低效)

public class SynchronizedLazySingleton {
    private static SynchronizedLazySingleton instance;
    
    private SynchronizedLazySingleton() {}
    
    // 方法级同步锁
    public static synchronized SynchronizedLazySingleton getInstance() {
        if (instance == null) {
            instance = new SynchronizedLazySingleton();
        }
        return instance;
    }
}

优劣对比​​:

  •  解决线程安全问题
  • 性能瓶颈​​:每次调用都同步,并发高时效率骤降

4. 双重检查锁(DCL)优化版

public class DoubleCheckedSingleton {
    private static volatile DoubleCheckedSingleton instance; // volatile确保可见性
    
    private DoubleCheckedSingleton() {}
    
    public static DoubleCheckedSingleton getInstance() {
        if (instance == null) { // 第一次检查(无锁)
            synchronized (DoubleCheckedSingleton.class) {
                if (instance == null) { // 第二次检查(有锁)
                    instance = new DoubleCheckedSingleton();
                }
            }
        }
        return instance;
    }
}

关键技术点​​:

  1. ​双重判空​​:外层避免不必要的锁竞争,内层防止重复创建
  2. ​volatile​​:禁止指令重排序,解决"半初始化"问题
  3. ​锁粒度优化​​:仅实例化阶段需要同步

⚠️ 注意:JDK 5+的volatile内存语义修正后此模式才安全

5. 静态内部类(推荐方案)

public class InnerClassSingleton {
    private InnerClassSingleton() {}
    
    // 静态内部类持有实例
    private static class SingletonHolder {
        private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
    }
    
    public static InnerClassSingleton getInstance() {
        return SingletonHolder.INSTANCE; // 首次调用时加载内部类
    }
}

优势​​:

  • 懒加载​​:调用getInstance()时才加载SingletonHolder
  • 无锁线程安全​​:JVM类加载机制保证初始化安全
  • 高性能​​:无同步开销

6. 枚举单例(最安全方案)

public enum EnumSingleton {
    INSTANCE; // 单例实例
    
    // 业务方法
    public void doSomething() {
        System.out.println("Singleton working");
    }
}
// 使用方式:EnumSingleton.INSTANCE.doSomething();

不可替代的优势​​:

  1. ​绝对单例​​:JVM保证枚举实例唯一性
  2. ​防反射攻击​​:无法通过反射创建新实例
  3. ​序列化安全​​:自动处理序列化/反序列化
  4. ​代码简洁​​:无需手动实现安全逻辑

三、单例模式进阶:陷阱防范与最佳实践

1. 反射攻击的防御策略

通过​​构造器检测​​阻止反射创建实例:

public class ReflectionProofSingleton {
    private static volatile ReflectionProofSingleton instance;
    
    private ReflectionProofSingleton() {
        // 防止反射破坏单例
        if (instance != null) {
            throw new IllegalStateException("单例实例已存在!");
        }
    }
    
    // 双重检查锁实现...
}

2. 序列化/反序列化保护

实现readResolve()方法保证反序列化返回同一实例

public class SerializableSingleton implements Serializable {
    ...
    
    // 反序列化时替换为现有实例
    private Object readResolve() {
        return getInstance();
    }
}

3. 多类加载器环境下的隐患

当存在​​多个类加载器​​时,每个加载器可能创建独立实例。解决方案:

ClassLoader.getSystemClassLoader().loadClass("SingletonClass");

四、五大黄金使用原则

  1. ​首选枚举实现​​——除非需要继承父类
  2. ​延迟加载需求​​→静态内部类
  3. ​启动即用资源​​→饿汉式
  4. ​避免DCL陷阱​​——必须配合volatile
  5. ​禁用synchronized方法​​——性能影响过大

    4

    8

五、经典面试难题解析

Q1:为什么需要两次判空检查?

第一次检查:​​避免不必要的锁竞争​​(已初始化时直接返回)
第二次检查:​​防止并发创建​​(多个线程通过第一关后,在锁内二次确认)

Q2:volatile在DCL中的作用?

  • ​禁止指令重排序​​:阻止new Singleton()操作被拆分为:
    ①分配内存 → ②返回引用 → ③初始化对象(正常顺序应为①→③→②)
  • ​保证可见性​​:确保所有线程看到完全初始化的对象

Q3:枚举如何防止反射攻击?

枚举类型在JVM层​​禁止反射调用构造器​​,Constructor#newInstance()方法内部会​​显式检查是否为枚举​​,若是则抛出IllegalArgumentException


​单例模式的选择决策树​​:

 最后忠告​​:在Spring等IoC容器环境中,​​避免滥用单例模式​​——容器管理的单例Bean已提供线程安全机制,手动实现反而导致依赖混乱

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

igxia

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值