一、什么是单例模式

单例模式(Singleton Pattern) 是 Java 中最简单也是最常用的设计模式之一。它确保一个类在整个应用运行期间只有一个实例存在,并提供一个访问该实例的全局访问点。

核心要点:

  • 私有构造器(Private Constructor):防止外部通过 new 关键字创建实例。
  • 私有静态实例(Private Static Instance):类内部维护唯一的实例对象。
  • 公共静态获取方法(Public Static getInstance):提供全局访问点。
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

二、单例模式的多种实现方式

1. 饿汉式(Eager Initialization)

类加载时就创建实例,简单可靠,天生线程安全。

public class EagerSingleton {
    // 类加载时立即初始化
    private static final EagerSingleton INSTANCE = new EagerSingleton();

    // 私有构造器
    private EagerSingleton() {
        // 防止反射破坏(可选加强)
        if (INSTANCE != null) {
            throw new RuntimeException("单例已存在,不允许反射创建");
        }
    }

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

优点:实现简单,无线程安全问题
缺点:如果实例创建成本高且从未使用,会造成资源浪费


2. 懒汉式(Lazy Initialization)

第一次调用 getInstance() 时才创建实例。

2.1 简单懒汉式(线程不安全)
public class LazySingletonUnsafe {
    private static LazySingletonUnsafe instance;

    private LazySingletonUnsafe() {}

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

问题:多个线程可能同时通过 if (instance == null) 检查,从而创建多个实例。

2.2 同步方法懒汉式(线程安全但性能差)
public class LazySingletonSync {
    private static LazySingletonSync instance;

    private LazySingletonSync() {}

    // 整个方法加锁,每次获取实例都要同步
    public static synchronized LazySingletonSync getInstance() {
        if (instance == null) {
            instance = new LazySingletonSync();
        }
        return instance;
    }
}

缺点:即使实例已创建,每次调用仍需竞争锁,高并发下性能低下。


3. 双重检查锁定(Double-Checked Locking)—— 推荐

在同步块前后各检查一次,结合 volatile 关键字保证可见性和禁止指令重排。

public class DCLSingleton {
    // volatile 保证可见性,并防止指令重排
    private static volatile DCLSingleton instance;

    private DCLSingleton() {}

    public static DCLSingleton getInstance() {
        if (instance == null) {                  // 第一次检查,避免不必要的同步
            synchronized (DCLSingleton.class) {
                if (instance == null) {          // 第二次检查,确保只创建一次
                    instance = new DCLSingleton();
                }
            }
        }
        return instance;
    }
}

为什么需要 volatile
instance = new DCLSingleton(); 这行代码在 JVM 中并非原子操作,大约分为三步:

  1. 分配内存空间
  2. 执行构造函数初始化对象
  3. 将 instance 指向分配的内存地址

JIT 编译器可能对指令进行重排序,变成 1→3→2。此时若另一个线程在第一个检查中发现 instance 不为 null,就直接返回,但获得的是一个尚未初始化完成的对象volatile 禁止了这种指令重排序。

优点:延迟加载,线程安全,高并发性能好


4. 静态内部类(Holder Class)—— 优雅的懒加载

利用类加载机制保证初始化实例时只有一个线程。既实现了延迟加载,又保证了线程安全,且写法简洁。

public class HolderSingleton {
    private HolderSingleton() {}

    // 静态内部类在第一次被引用时才会加载
    private static class SingletonHolder {
        private static final HolderSingleton INSTANCE = new HolderSingleton();
    }

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

原理:JVM 保证类的静态变量初始化是线程安全的,而且静态内部类只有在调用 getInstance() 时才会被加载,实现了延迟实例化。

优点:写法简洁,天生线程安全,无性能缺陷


5. 枚举单例(Enum Singleton)—— 最安全的实现

由 Joshua Bloch(Java 著名架构师)在《Effective Java》中推荐。无偿提供了序列化机制,并绝对防止多次实例化(包括反射攻击),是目前最安全的单例实现。

public enum EnumSingleton {
    INSTANCE;   // 单例实例

    // 可以添加方法和字段
    private String config;

    public String getConfig() {
        return config;
    }

    public void setConfig(String config) {
        this.config = config;
    }

    // 业务方法
    public void doSomething() {
        System.out.println("枚举单例执行操作");
    }
}

// 使用
EnumSingleton.INSTANCE.doSomething();

枚举的优势

  • 天然防反射攻击(反射 newInstance 会抛出异常)
  • 天然防序列化破坏(序列化后反序列化仍是同一实例)
  • 写法极简

三、单例模式的“破坏”与防御

即使精心实现的单例,也可能被以下方式破坏:

1. 反射攻击及防御

// 反射强行调用私有构造器
Constructor<DCLSingleton> constructor = DCLSingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
DCLSingleton instance1 = constructor.newInstance();
DCLSingleton instance2 = DCLSingleton.getInstance();
System.out.println(instance1 == instance2); // false,单例被破坏

防御方法:在构造器中添加标志位判断

public class SafeSingleton {
    private static volatile boolean flag = false;

    private SafeSingleton() {
        if (flag) {
            throw new RuntimeException("单例已被创建,禁止反射攻击");
        }
        flag = true;
    }
    // ... 其余代码
}

注意:若攻击者先修改 flag 字段的值,该防御仍可能被绕过。枚举没有此类问题。

2. 序列化破坏及防御

Java 序列化/反序列化会创建新对象,破坏单例。

// 序列化后反序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.obj"));
oos.writeObject(instance);

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.obj"));
SafeSingleton newInstance = (SafeSingleton) ois.readObject();
System.out.println(instance == newInstance); // false

防御方法:添加 readResolve() 方法

public class SerializableSingleton implements Serializable {
    private static final long serialVersionUID = 1L;
    private static final SerializableSingleton INSTANCE = new SerializableSingleton();

    private SerializableSingleton() {}

    public static SerializableSingleton getInstance() {
        return INSTANCE;
    }

    // 反序列化时自动调用此方法,返回已有实例
    protected Object readResolve() {
        return INSTANCE;
    }
}

枚举单例自带此特性,无需额外处理。


四、单例模式的优缺点与适用场景

优点

  • 严格控制实例数量,节约系统资源
  • 提供全局访问点,方便统一管理
  • 扩展性强(可从单例扩展为多例)

缺点

  • 没有抽象层,扩展困难
  • 职责过重,可能违反单一职责原则
  • 全局状态可能带来隐式耦合
  • 在并发场景下如果设计不当会引发问题

适用场景

  • 需要频繁创建和销毁的对象(如线程池、数据库连接池)
  • 有状态的工具类对象
  • 需要控制资源访问的对象(如配置文件管理器、日志管理器)
  • Windows 任务管理器、回收站等系统级服务
  • Spring 中默认 Bean 作用域(singleton)

五、Spring 框架中的单例

Spring 容器中的 Bean 默认就是单例模式,但它的实现更为灵活:

  • 通过 ConcurrentHashMap 维护单例缓存池
  • 采用三级缓存解决循环依赖
  • 作用域可通过 @Scope("prototype") 改变
  • 底层并非使用传统设计模式,而是基于容器的统一管理

六、总结对比

实现方式 线程安全 懒加载 防反射 防序列化 性能 推荐指数
饿汉式 ⭐⭐
懒汉式(sync)
双重检查锁 ⭐⭐⭐⭐
静态内部类 ⭐⭐⭐⭐
枚举 ❌(饿汉) ⭐⭐⭐⭐⭐

推荐选择策略

  • 日常开发直接用枚举,除非需要懒加载
  • 需要懒加载且不需要防御反射/反序列化:双重检查锁静态内部类
  • 框架/容器环境(如 Spring):交给容器管理

七、完整示例

下面是一个综合了防御机制的工业级饿汉式单例:

import java.io.Serializable;

public final class CosmicSingleton implements Serializable {
    private static final long serialVersionUID = 42L;
    
    // 饿汉式实例
    private static final CosmicSingleton INSTANCE = new CosmicSingleton();
    
    // 反射防御标志
    private static final boolean INITIALIZED;
    
    static {
        // 类加载时自动执行,确保单例创建
        INITIALIZED = true;
    }
    
    private CosmicSingleton() {
        // 防御反射攻击
        if (INITIALIZED) {
            throw new RuntimeException("Use getInstance() to access singleton");
        }
    }
    
    public static CosmicSingleton getInstance() {
        return INSTANCE;
    }
    
    // 防御序列化破坏
    protected Object readResolve() {
        return INSTANCE;
    }
    
    // 业务方法
    public void showMessage() {
        System.out.println("Hello, Cosmic Singleton!");
    }
}

掌握单例模式的这些实现与变体,你就能在任何 Java 场景中游刃有余地控制对象的唯一性了。

更多推荐