Skip to content

单例模式

字数: 0 字 时长: 0 分钟

简介

单例模式(Singleton pattern),它确保一个类只有一个实例,并且提供一个全局访问点

优点:

  • 节省资源:控制实例数量,避免在高频场景中频繁创建和销毁对象
  • 统一管理:全局只有一个入口,方便管理,使用方便
  • 线程安全:好的单例实现保证只创建一次,避免竞态条件

缺点:

  • 不利于测试和扩展:单例很难在单元测试时替换成 mock 对象
  • 潜在的状态问题:单例属于共享资源,一个地方修改它的状态,可能导致其他地方出现问题
  • 生命周期不可控:单例通常在 JVM 生命周期内都存在,可能导致资源无法及时释放

UML 类图

singleton.webp

  • 私有化构造器:禁止外部通过构造器 new 新的对象实例(不过无法防止反射)
  • 唯一实例变量:通常写为 private static Singleton instance
  • 全局访问点:提供一个全局访问点 public static Singleton getInstance()
  • 线程安全:通过加锁、双检锁、静态内部类、枚举等实现方式保证线程安全

实现示例

1. 饿汉式

在类加载阶段就完成了实例的初始化,线程安全,但无法实现延迟加载。

java
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

2. 懒汉式

懒汉式在第一次访问时才创建实例(延迟加载),使用 synchronized 实现线程安全,但每次访问 getInstance() 都需要同步,性能较低。

java
public class Singleton {
    private static Singleton INSTANCE;
    private Singleton() {}
    // 对访问方法加锁保证线程安全
    public synchronized Singleton getInstance() {
        if (INSTANCE == null){
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
}

3. 双检锁 (最佳实践)

既做到了延迟加载又保证了访问时的高性能,使用 volatile 防止指令重排序,双重 null 值检查,保证后续访问时无锁。

java
public class Singleton {
    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;
    }
}

4. 静态内部类

利用 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;
    }
}

5. 枚举

《Effective Java》 中提出的枚举实现单例模式,枚举值在类加载时就创建,线程安全、天生防止反射破坏、自动处理序列化、简洁优雅

java
public enum Singleton {
    INSTANCE;
}

单例的破坏和防护

1. 反射防护

单例模式只是私有化了构造器,防止外部直接 new 创建实例,但是外部可以通过反射直接调用构造器创建实例。因此可以在构造方法中添加一个判断,如果实例已经创建了,则抛出一个异常,阻止外部创建实例。

java
public class Singleton {
    private static boolean instantiated = false;
    private Singleton(){
        if(instantiated){
            throw new IllegalStateException("已存在实例");
        }
        instantiated = true;
    }
}

2. 序列化安全

本质还是反射破坏单例,因为反序列化时默认会通过反射调用私有的构造方法创建实例,破坏了单例。

readObject 方法是 JAVA 序列化机制中的一种特殊回调方法。当对象被反序列化时,若定义了 readObject 方法,则 JVM 会调用这个方法,用其返回值作为反序列化生成的对象

java
public class Singleton implements Serializable {
    private static final Singleton instance = new Singleton();
    private Singleton() {}
    protected Object readResolve() {
        return instance;
    }
}

3. 克隆防护

clone() 也可以破坏单例,可以参照放射防护的办法,重写 clone() 方法,在方法中抛出异常

java
public class Singleton implements Cloneable {
    private static final Singleton instance = new Singleton();
    private Singleton() {}
    @Override
    protected Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }
}

Spring 单例

Spring 单例不同于传统单例模式,Spring 单例是容器单例,生命周期由容器管理,支持依赖注入(传统单例很难实现),通过 ConcurrentHashMap 实现线程安全

singleton2.webp