结论先行
- 饿汉式:线程安全但可能造成资源浪费,推荐在初始化成本低的场景使用
- 懒汉式:需要解决线程安全问题,推荐使用双重检查锁+volatile优化
- 静态内部类:最佳实践方案,完美平衡延迟加载与线程安全
- 枚举单例:JDK1.5+推荐方案,天然防反射/序列化破坏
- 实际开发中优先选择枚举或静态内部类实现
文章持续更新,可以微信搜一搜「 半个脑袋儿 」第一时间阅读
一、核心实现方式
1. 饿汉式
- class ClassA {
- private static final ClassA INSTANCE = new ClassA();
-
- public static ClassA getInstance() {
- return INSTANCE;
- }
-
- private ClassA() {} // 防止反射创建
- }
复制代码 特点:
- 类加载时立即初始化(可能造成资源浪费)
- 天然线程安全
- 需处理反射攻击(添加私有构造器判空逻辑)
2. 双重检查锁懒汉式
- class ClassB {
- private static volatile ClassB instance;
-
- public static ClassB getInstance() {
- if (instance == null) { // 第一次检查
- synchronized (ClassB.class) { // 同步锁
- if (instance == null) { // 第二次检查
- instance = new ClassB();
- }
- }
- }
- return instance;
- }
-
- private ClassB() {}
- }
复制代码 关键点:
- volatile防止指令重排序(JDK5+的JMM修复)
- 两次null检查确保性能与线程安全
- 仍可能被反射破坏单例
3. 静态内部类
- class ClassC {
- private static class Holder {
- static final ClassC INSTANCE = new ClassC();
- }
-
- public static ClassC getInstance() {
- return Holder.INSTANCE;
- }
-
- private ClassC() {}
- }
复制代码 优势:
- 利用类加载机制保证线程安全
- 实现延迟加载(调用getInstance时才会初始化)
- 代码简洁无锁
4. 枚举式
- enum EnumSingleton {
- INSTANCE;
-
- public void businessMethod() {
- // 业务方法
- }
- }
复制代码 绝对优势:
- 天生防反射攻击(枚举类没有构造器)
- 自动处理序列化/反序列化
- 代码极度简洁
二、实战应用场景
1. 配置管理类
- public enum ConfigManager {
- INSTANCE;
-
- private Properties props = new Properties();
-
- ConfigManager() {
- try(InputStream is = getClass().getResourceAsStream("/app.properties")) {
- props.load(is);
- }
- }
-
- public String getProperty(String key) {
- return props.getProperty(key);
- }
- }
复制代码 2. 数据库连接池
- public class ConnectionPool {
- private static final int MAX_SIZE = 100;
- private BlockingQueue<Connection> pool = new ArrayBlockingQueue<>(MAX_SIZE);
-
- private static class Holder {
- static final ConnectionPool INSTANCE = new ConnectionPool();
- }
-
- private ConnectionPool() {
- // 初始化连接池
- }
-
- public static ConnectionPool getInstance() {
- return Holder.INSTANCE;
- }
-
- public Connection getConnection() throws InterruptedException {
- return pool.take();
- }
- }
复制代码 3. Spring中的单例
- Spring默认的Bean作用域就是单例
- 通过IOC容器管理生命周期
- 与设计模式单例的区别:每个容器对应一个实例
三、高频面试题解析
Q1:DCL(双重检查锁)为什么要加volatile?
答:防止指令重排序导致返回未初始化完成的对象。new操作不是原子操作,分为:
不加volatile可能导致步骤2和3重排序,其他线程可能拿到未初始化完成的对象。
Q2:如何防止反射攻击?
- private ClassC() {
- if (Holder.INSTANCE != null) {
- throw new RuntimeException("禁止反射创建!");
- }
- }
复制代码 Q3:枚举单例如何防止反射?
- 枚举类的构造方法由JVM特殊处理
- 反射newInstance方法会直接抛出异常
Q4:单例对象什么时候会被回收?
- 只有当加载该类的ClassLoader被回收时才会被回收
- 一般情况(使用系统类加载器)会与JVM生命周期一致
Q5:单例模式的优缺点?
优点:
- 内存中只有一个实例,减少内存开销
- 避免对资源的多重占用
缺点:
- 违背单一职责原则(既要管理实例又要处理业务)
- 扩展困难(需要修改源码)
- 测试困难(全局状态难以隔离)
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |