一、什么是单例模式?
单例模式是 Java 中最基础、最常用的创建型设计模式 ,它的核心定义非常简单:
保证一个类在整个应用程序中,有且仅有一个实例对象,并且提供一个全局访问该实例的方法。
简单理解:就像一个程序里的「唯一工具」,比如项目中的配置管理器、数据库连接池、日志工具类,全程只需要一个对象统一管理,不能随意创建多个,避免资源浪费和逻辑混乱。
单例模式的核心特性
- 唯一实例:类只能被实例化一次,内存中永远只有一个对象;
- 全局访问:提供一个公共的静态方法,让外部可以直接获取这个唯一实例;
- 私有构造 :构造方法私有化,禁止外部通过 new 关键字创建对象。
二、为什么要用单例模式?(核心优势)
在实际开发中,单例模式能解决这些关键问题:
- 节约系统资源:避免频繁创建 / 销毁对象(如数据库连接、线程池),减少内存开销;
- 保证数据统一:全局只有一个实例,避免多个实例导致的数据不一致、状态冲突;
- 统一管理逻辑:适合全局配置、全局缓存、全局日志等需要统一控制的场景。
适用场景:
- 数据库连接池、Redis 连接池
- 项目全局配置类
- 日志工具类
- 线程池、任务管理器
- 计数器(统计在线人数、接口访问量)
三、Java 单例模式的 5 种实现方式
单例模式的实现有多种写法,我们从最简单到最安全、最高效,逐一讲解。
前置规则(所有写法都必须遵守)
- 构造方法私有化(private 修饰),杜绝外部 new;
- 内部创建唯一的私有实例;
- 提供公共静态方法(getInstance())供外部获取实例。
1. 饿汉式(静态常量)- 最简单、线程安全
核心思想 :类加载时就直接创建实例,天生线程安全,没有并发问题。
因为 "急切创建",所以叫饿汉式。
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java public class Singleton { // 1. 私有静态常量,类加载时就初始化 private static final Singleton INSTANCE = new Singleton(); // 2. 私有构造方法,禁止外部实例化 private Singleton() {} // 3. 公共静态方法,返回唯一实例 public static Singleton getInstance() { return INSTANCE; } } |
优点:
- 写法最简单
- 类加载时初始化,线程安全
- 没有锁,执行效率高
缺点:
- 类加载就创建实例,可能造成内存浪费(如果这个类一直不用,实例也会占内存)
使用建议:适用于实例占用内存小、一定会被使用的场景。
2. 懒汉式(线程不安全)- 按需创建,多线程禁用
核心思想 :用到实例时才创建(懒加载),但多线程环境下会产生多个实例,线程不安全。
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java public class Singleton { private static Singleton instance; private Singleton() {} // 外部调用时才创建实例 public static Singleton getInstance() { // 线程A和线程B同时判断到这里,会创建两个实例 if (instance == null) { instance = new Singleton(); } return instance; } } |
优点 :实现懒加载,节约内存
缺点 :多线程环境下完全不可用,会破坏单例特性
结论:工作中绝对不要用!
3. 懒汉式(同步方法,线程安全)- 效率低
核心思想 :给 getInstance() 方法加 synchronized 锁,保证线程安全,但效率极低。
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java public class Singleton { private static Singleton instance; private Singleton() {} // 方法加锁,同一时间只能有一个线程进入 public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } |
优点 :线程安全,支持懒加载
缺点 :加锁粒度太大,每次获取实例都要锁,高并发下性能差
使用建议:不推荐用于高并发项目。
4. 双重检查锁(DCL)- 推荐!兼顾线程安全 + 效率 + 懒加载
全称 :Double Check Lock,简称 DCL
核心思想 :两次判空 + 一次锁 + volatile 关键字,既保证线程安全,又保证高性能,是企业开发最常用写法。
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 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?
instance = new Singleton() 不是原子操作,JVM 可能会指令重排,导致线程获取到未初始化完成的实例。volatile 可以禁止指令重排,保证线程安全。
优点:
- 线程安全
- 懒加载(用到才创建)
- 加锁粒度小,性能极高
- 兼顾内存和效率
结论 :这是绝大多数场景的最优解,推荐优先使用!
5. 静态内部类 - 极简优雅,线程安全
核心思想:利用 JVM 类加载机制保证线程安全,写法极简,也是推荐方案。
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java 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 保证类加载过程线程安全;
- 天然实现懒加载 + 线程安全。
优点:
- 代码极简
- 线程安全
- 懒加载
- 无锁,性能极高
缺点:无法传递参数(不适合需要初始化参数的单例)
使用建议:不需要传参的场景,首选这种写法!
6. 枚举单例 - 最安全、防反射破坏
核心思想 :Java 枚举天然单例,绝对防止反射、序列化破坏单例,是《Effective Java》推荐的最佳写法。
|-----------------------------------------------------------------------------------------------------------------------------|
| java public enum Singleton { // 唯一实例 INSTANCE; // 可以添加自己的方法 public void doSomething() { System.out.println("枚举单例执行方法"); } } |
使用方式:
|-----------------------------------------------------------------------|
| java Singleton instance = Singleton.INSTANCE; instance.doSomething(); |
优点:
- 最简单
- 绝对线程安全
- 防止反射攻击、序列化破坏
- 无需考虑并发问题
缺点:不支持懒加载(枚举类加载时就创建实例)
使用建议:追求极致安全、不需要懒加载时,这是完美方案!
四、单例模式的安全问题与解决
1. 反射破坏单例
通过反射可以强制调用私有构造方法,创建新实例,破坏 DCL、静态内部类、饿汉式单例。
解决方案:
- 首选枚举单例(天然防反射)
- 构造方法中加判断,重复创建抛出异常:
|----------------------------------------------------------------------------------------------------|
| java private Singleton() { if (instance != null) { throw new RuntimeException("禁止通过反射创建单例实例"); } } |
2. 序列化破坏单例
对象序列化后再反序列化,会创建新对象。
解决方案:
- 枚举单例天然防序列化
- 普通单例类添加 readResolve() 方法:
|--------------------------------------------------------|
| java private Object readResolve() { return instance; } |
五、五种单例写法对比总结
|------------|------|-----|----|-------|
| 写法 | 线程安全 | 懒加载 | 性能 | 推荐指数 |
| 饿汉式 | ✅ | ❌ | 高 | ⭐⭐⭐ |
| 懒汉式(不安全) | ❌ | ✅ | 高 | ❌ |
| 懒汉式(同步方法) | ✅ | ✅ | 低 | ⭐ |
| 双重检查锁(DCL) | ✅ | ✅ | 极高 | ⭐⭐⭐⭐⭐ |
| 静态内部类 | ✅ | ✅ | 极高 | ⭐⭐⭐⭐⭐ |
| 枚举单例 | ✅ | ❌ | 高 | ⭐⭐⭐⭐⭐ |
六、总结
- 单例核心:一个类只有一个实例,全局访问,私有构造;
- 最优通用方案:双重检查锁(DCL)、静态内部类;
- 最安全方案:枚举单例;
- 使用场景:全局配置、连接池、线程池、工具类;
- 避坑指南:避免使用线程不安全的懒汉式,高并发优先用 DCL。
单例模式是设计模式的入门基石,理解它的实现原理和优缺点,能帮你写出更健壮、更高效的 Java 代码。
总结
- 单例模式保证全局唯一实例 ,核心是私有构造 + 静态实例 + 全局获取方法;
- 日常开发优先用 双重检查锁(DCL) 和 静态内部类;
- 追求极致安全选 枚举单例 ,简单场景选 饿汉式;
- 警惕反射、序列化破坏单例,做好防御处理。