Java设计模式-单例模式

概述

单例模式(Singleton Pattern)是 Java 中最常用、最基础的创建型设计模式之一。

它的核心目标是:确保一个类在整个应用程序生命周期中只有一个实例,并提供一个全局访问点。

适用于:

1、需要频繁创建和销毁的对象;

2、创建消耗资源太多有会频繁创建的对象;

3、有状态的工具类对象;

4、频繁访问数据库或文件的对象。

在 Java 中,这意味着:

该类不能被外部随意 new

整个 JVM 中只存在一个该类的对象;

所有需要使用该对象的地方都通过同一个入口获取。

典型场景

场景 说明
配置管理器 应用启动时加载一次配置,后续所有模块共享同一份配置
日志记录器 避免多个 Logger 实例导致日志混乱或文件锁冲突
数据库连接池 连接池本身应是全局唯一的资源管理中心
缓存中心 如内存缓存(如本地 Map 缓存),需统一管理
计数器/ID 生成器 需保证全局唯一性

核心要素

私有构造方法:防止外部通过 new 创建实例。

持有自身静态实例:通常用 private static 修饰。

提供公共静态方法获取实例:如 getInstance()。

常见实现方式

1. 饿汉式(Eager Initialization)
java 复制代码
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

优点:线程安全、实现简单

缺点:类加载时就创建实例,可能浪费资源(即使没用到)

2. 懒汉式(Lazy Initialization)--- 非线程安全
java 复制代码
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

多线程下可能创建多个实例,不推荐使用。

3. 懒汉式 + 同步方法(线程安全但低效)
java 复制代码
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

线程安全

每次调用都要同步,性能差

4. 双重检查锁定(Double-Checked Locking) 推荐
java 复制代码
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,防止 JVM 指令重排导致未初始化完成的对象被引用。

关键点:volatile 的作用

instance = new DCLSingleton(); 实际分三步:

  1. 分配内存空间
  2. 调用构造方法,初始化对象
  3. instance 指向分配的内存地址

JVM 可能重排序为:1 → 3 → 2(指令重排优化)

如果没有 volatile

  • 线程 A 执行到第 3 步(instance != null),但对象未初始化完成
  • 线程 B 调用 getInstance(),看到 instance != null,直接返回
  • B 使用了一个未完全构造的对象空指针或状态错误!

volatile 禁止指令重排序 + 保证可见性

5. 静态内部类(推荐)
java 复制代码
public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

线程安全(由 JVM 类加载机制保证)

延迟加载(只有调用 getInstance() 时才加载内部类)

代码简洁、无锁、性能高

强烈推荐用于大多数场景

6. 枚举方式(最安全)
java 复制代码
public enum Singleton {
    INSTANCE;

    public void doSomething() {
        // 业务方法
    }
}

天然线程安全

防止反射攻击

防止反序列化破坏单例(枚举的序列化机制特殊)

《Effective Java》作者 Joshua Bloch 强烈推荐

使用方式:Singleton.INSTANCE.doSomething();

为什么最安全
攻击方式 普通单例 枚举单例
反射 可通过 setAccessible(true) 调用私有构造器 枚举构造器不能被反射调用(JVM 层面禁止)
反序列化 若未实现 readResolve(),会创建新对象 枚举的序列化机制特殊,反序列化返回原实例
多实例 可能因上述原因被破坏 绝对保证单例

缺点:无法继承(枚举隐式继承 Enum);不能懒加载(其实枚举实例也是类加载时初始化,类似饿汉式);部分框架(如某些老 ORM)对枚举支持不好。

7、性能对比

实现方式 线程安全 延迟加载 性能 安全性
饿汉式
懒汉式(无锁)
synchronized 方法
DCL(volatile) 高(需正确实现)
静态内部类
枚举 ❌(类加载时) 最高

📌 实际性能差异在现代 JVM 上极小,安全性 > 微优化

注意事项

问题 说明
反射攻击 通过反射可调用私有构造器,破坏单例(枚举可防御)
序列化/反序列化 反序列化可能创建新对象,需实现 readResolve() 方法: private Object readResolve() { return INSTANCE; }
多类加载器 不同 ClassLoader 可能加载多个单例(一般应用无需考虑)

常见误区

误区1:单例能解决所有"全局状态"问题

→ 单例本质是全局状态,过度使用会导致:

  • 代码耦合度高
  • 难以单元测试(需 mock 单例)
  • 隐藏依赖(调用方不显式传参)

替代方案:考虑依赖注入(DI),如 Spring 的 @Component + 单例作用域

误区2:单例一定是"好"的

→ 如果对象状态会变,且被多线程修改,仍需同步控制(单例 ≠ 线程安全对象)

误区3:Spring 中的 Bean 默认是单例,所以不用自己写

→ 正确!在 Spring 应用中,通常用 @Service / @Component 即可,容器管理生命周期。

如何选择

你的需求 推荐实现
追求极致安全、不怕饿汉式 枚举
需要延迟加载、代码清晰 静态内部类
老项目、必须兼容 JDK 1.4 DCL(但尽量升级)
Spring 项目 直接用 @Component,别手写单例
相关推荐
刘一说6 小时前
Java 中实现多租户架构:数据隔离策略与实践指南
java·oracle·架构
beata6 小时前
Java基础-9:深入 Java 虚拟机(JVM):从底层源码到核心原理的全面解析
java·后端
SimonKing6 小时前
分享一款可以管理本地端口的IDEA插件:Port Manager
java·后端·程序员
索荣荣6 小时前
Maven配置文件(pom.xml)终极指南
java·开发语言
代码栈上的思考6 小时前
SpringBoot 拦截器
java·spring boot·spring
送秋三十五7 小时前
一次大文件处理性能优化实录————Java 优化过程
java·开发语言·性能优化
雨中飘荡的记忆7 小时前
千万级数据秒级对账!银行日终批处理对账系统从理论到实战
java
jbtianci7 小时前
Spring Boot管理用户数据
java·spring boot·后端
Sylvia-girl7 小时前
线程池~~
java·开发语言
魔力军7 小时前
Rust学习Day3: 3个小demo实现
java·学习·rust