设计模式之【单例模式】
单例模式(Singleton Pattern)
单例模式是设计模式中创建型模式的经典代表,核心思想是:保证一个类在整个应用程序中只有一个实例,并提供一个全局访问点来获取这个实例。
简单来说,单例模式就是让某个类 “独一无二”—— 无论调用多少次创建方法,返回的都是同一个对象,避免重复创建对象消耗资源(比如数据库连接池、配置类、日志工具类等场景)。
单例模式的实际应用场景
工具类:比如 java.lang.Runtime(JDK 内置饿汉单例)、日志工具类、日期工具类;
资源密集型对象:数据库连接池、Redis 连接池、线程池(避免重复创建消耗资源);
配置类:全局配置类(应用启动后加载一次配置,全局复用);
Spring 容器:Spring 中默认所有 Bean 都是单例(通过容器管理唯一实例);
缓存:应用级缓存(比如本地缓存工具,单例保证缓存数据全局一致)。
一、核心设计原则
私有构造方法:禁止外部通过 new 关键字创建实例;
私有静态实例:类内部维护唯一的实例对象;
公共静态方法:提供全局访问点,返回这个唯一实例。
二、Java 中常见的单例实现方式(从易到难,从有坑到最优)
单例模式的实现关键是线程安全和懒加载(按需创建实例),不同实现方式的核心差异也在这两点。
1. 饿汉式(饿汉单例)—— 最简单,线程安全但无懒加载
核心逻辑:类加载时就创建实例(“饿” 意味着迫不及待创建),JVM 类加载机制保证线程安全。
public class HungrySingleton { // 1. 私有静态实例:类加载时直接创建(唯一实例) private static final HungrySingleton INSTANCE = new HungrySingleton(); // 2. 私有构造方法:禁止外部new private HungrySingleton() {} // 3. 公共静态方法:返回唯一实例 public static HungrySingleton getInstance() { return INSTANCE; } // 测试方法 public void doSomething() { System.out.println("饿汉单例执行方法,实例地址:" + this); } }特点:
✅ 线程安全(JVM 类加载时仅创建一次实例);
❌ 无懒加载(类加载时就创建,即使没用到也会占用内存);
❌ 无法控制实例创建时机(比如需要读取配置后再创建的场景不适用);
✅ 实现简单,无并发问题。
2. 懒汉式(懒汉单例)—— 懒加载但非线程安全(基础版)
核心逻辑:第一次调用 getInstance() 时才创建实例(“懒” 意味着按需创建),但多线程下会创建多个实例。
public class LazySingleton { // 1. 私有静态实例:初始为null(懒加载) private static LazySingleton INSTANCE = null; // 2. 私有构造方法 private LazySingleton() {} // 3. 公共静态方法:首次调用时创建实例 public static LazySingleton getInstance() { if (INSTANCE == null) { // 判空:未创建则new INSTANCE = new LazySingleton(); } return INSTANCE; } public void doSomething() { System.out.println("懒汉单例执行方法,实例地址:" + this); } }问题:
多线程场景下,多个线程同时进入 if (INSTANCE == null),会创建多个实例(破坏单例特性)。比如:
// 多线程测试 public class SingletonTest { public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(() -> { LazySingleton instance = LazySingleton.getInstance(); instance.doSomething(); }).start(); } } }输出会看到多个不同的实例地址,说明单例被破坏。
3.懒汉式(加锁版)—— 线程安全但性能差
核心逻辑:给 getInstance() 加 synchronized 锁,保证同一时间只有一个线程执行创建逻辑。
public class LazySingletonWithLock { private static LazySingletonWithLock INSTANCE = null; private LazySingletonWithLock() {} // 加synchronized锁,保证线程安全 public static synchronized LazySingletonWithLock getInstance() { if (INSTANCE == null) { INSTANCE = new LazySingletonWithLock(); } return INSTANCE; } }特点:
✅ 线程安全;
✅ 懒加载;
❌ 性能差(每次调用 getInstance() 都要加锁,即使实例已创建)。
4. 双重检查锁(DCL)—— 线程安全 + 懒加载 + 高性能(推荐)
核心逻辑:两次判空 + 加锁,既保证线程安全,又避免频繁加锁(最优的懒加载单例)。
public class DclSingleton { // 关键:volatile 关键字,禁止指令重排(必须加!) private static volatile DclSingleton INSTANCE = null; private DclSingleton() {} public static DclSingleton getInstance() { // 第一次判空:实例已创建时,直接返回,无需加锁(提升性能) if (INSTANCE == null) { synchronized (DclSingleton.class) { // 类锁,只锁创建逻辑 // 第二次判空:防止多个线程等待锁后重复创建 if (INSTANCE == null) { INSTANCE = new DclSingleton(); } } } return INSTANCE; } }关键细节:
volatile 必须加:INSTANCE = new DclSingleton() 不是原子操作,分为 3 步:
分配内存;2. 初始化对象;3. 把 INSTANCE 指向内存地址。
不加 volatile 可能发生指令重排(步骤 2 和 3 交换),导致其他线程拿到 “未初始化完成的实例”。
双重判空:第一次判空避免频繁加锁,第二次判空避免多线程重复创建。
特点:
✅ 线程安全;
✅ 懒加载;
✅ 高性能(仅首次创建时加锁);
✅ 实际开发中最常用(比如 Spring 中很多单例 Bean 底层用此方式)。
5. 静态内部类(Holder 模式)—— 线程安全 + 懒加载(优雅版)
核心逻辑:利用 JVM 静态内部类的加载机制,实现懒加载 + 线程安全(无需加锁)。
public class HolderSingleton { // 私有构造方法 private HolderSingleton() {} // 静态内部类:只有调用 getInstance() 时才加载 private static class SingletonHolder { // 内部类加载时创建实例,JVM 保证线程安全 private static final HolderSingleton INSTANCE = new HolderSingleton(); } // 公共方法:返回内部类中的实例 public static HolderSingleton getInstance() { return SingletonHolder.INSTANCE; } }特点:
✅ 线程安全(JVM 加载内部类时仅创建一次实例);
✅ 懒加载(内部类只有被调用时才加载);
✅ 无锁,性能高;
✅ 代码优雅,推荐使用(比 DCL 更简洁)。
6. 枚举单例 —— 绝对线程安全 + 防反射破坏(终极版)
核心逻辑:利用 Java 枚举的特性,天然保证单例(无法通过反射 / 序列化破坏)。
public enum EnumSingleton { // 唯一实例(枚举常量) INSTANCE; // 业务方法 public void doSomething() { System.out.println("枚举单例执行方法,实例地址:" + this); } } // 直接通过枚举常量获取实例 EnumSingleton instance = EnumSingleton.INSTANCE; instance.doSomething();特点:
✅ 绝对线程安全(JVM 保证枚举常量唯一);
✅ 防止反射破坏(反射无法创建枚举实例);
✅ 防止序列化破坏(枚举序列化机制特殊,反序列化仍返回同一实例);
❌ 无懒加载(枚举类加载时创建实例);
✅ 《Effective Java》推荐的最优单例方式(无任何坑)。
特点总结
单例模式的核心是保证类只有一个实例 + 全局访问点,核心价值是节省资源、保证状态一致;
实际开发中优先选 双重检查锁(DCL) 或 静态内部类(懒加载 + 高性能),追求绝对安全选 枚举单例;
避免使用基础版懒汉式(线程不安全)和加锁懒汉式(性能差);
单例模式的坑主要在线程安全和指令重排,DCL 中 volatile 必须加,枚举单例可规避所有坑。
