【创建型设计模式】单例模式

【创建型设计模式】单例模式

这篇博客接下来几篇都将阐述设计模式相关内容。

接下来的顺序大概是:单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。

一、什么是单例模式

单例模式是一种创建型设计模式,它保证一个类仅有一个实例,并提供一个全局访问点。

核心思想:

  1. 限制类的实例化次数,确保全局只有一个实例。
  2. 提供统一访问该实例的方法。

Client 为客户端,Singleton 是单例类,通过调用 Singleton.getInstance() 来获取实例对象。

二、单例模式的 6 种方法

(1) 饿汉模式
java 复制代码
public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton() {}
    
    public static Singleton getInstance() {
        return instance;
    }
}

特点:

  • 初始化时间: 类加载时即完成实例化。
  • 访问效率: 调用 getInstance 时无需等待,性能高。

优点:

  • 基于类加载机制,天然线程安全。
  • 实现简单,适用于单例对象较少被频繁初始化的场景。

缺点:

  • 无法延迟加载,如果实例从未被使用,会浪费内存。
(2) 懒汉模式(线程不安全)
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;
    }
}

特点:

  • 线程安全性: 使用 synchronized 关键字确保多线程下只生成一个实例。

优点: 在多线程环境中保证安全。

缺点: 同步锁导致性能下降,每次调用 getInstance 都需排队,影响效率。

(4) 双重检查模式(DCL)
java 复制代码
public class Singleton {
    // 1. 声明 volatile 修饰的静态实例变量
    private static volatile Singleton instance;
    
    // 2. 私有化构造方法,防止外部直接实例化
    private Singleton() {}

    // 3. 提供对外获取实例的静态方法
    public static Singleton getInstance() {
        // 第一次检查:避免不必要的同步
        if (instance == null) {
            synchronized (Singleton.class) { // 加锁
                // 第二次检查:确保实例未被其他线程创建
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

特点:

  • 性能优化: 减少同步开销。
  • 线程安全: 使用 volatile 避免指令重排序,确保多线程环境下正确创建实例。

优点: 资源利用率高,延迟初始化,线程安全,性能优于懒汉模式(线程安全版)。

缺点: 实现复杂,某些情况下可能出现 DCL 失效问题(如旧版 JVM)。

DCL 模式的核心思想

DCL 的核心是两次检查同步锁的结合。

**第一次检查(if (instance == null)):**在大多数情况下,实例已经被初始化,可以直接返回,不需要加锁。减少了不必要的同步,提高性能。

**加锁(synchronized):**在实例尚未初始化时,进入临界区,防止多个线程同时创建实例。

**第二次检查(if (instance == null)):**加锁后,再次检查实例是否为 null,防止多个线程同时通过第一次检查,确保单例对象只被创建一次。

**volatile 关键字:**确保 instance 的修改对所有线程立即可见,防止指令重排序导致未完全初始化的对象被其他线程访问。

为什么需要 volatile

问题:指令重排序

在没有 volatile 修饰时,JVM 编译器和 CPU 可能对以下代码进行优化:

java 复制代码
instance = new Singleton();

这一行代码可能被分解为以下三步:

  1. 为对象分配内存。
  2. 初始化对象。
  3. 将对象引用赋值给 instance

但实际执行中,可能发生指令重排序

  1. 为对象分配内存。
  2. 将对象引用赋值给 instance
  3. 初始化对象。

如果线程 A 在步骤 2 后被切换,线程 B 进入并访问了 instance,此时会得到一个未初始化的对象,导致程序行为不可预期。

解决方法: volatile 关键字禁止指令重排序,确保对象初始化完成后才将引用赋值给 instance

(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;
    }
}

特点:

  • 延迟加载: 第一次调用 getInstance 时才加载内部类,完成实例化。
  • 线程安全: 内部类加载机制天然线程安全。

优点:

  • 实现简单,避免同步开销。
  • 兼具延迟加载和线程安全,推荐使用。

缺点: 无法控制实例销毁,适用单例生命周期较长的场景。

静态内部类的加载时机:

在 Java 中,类的加载是延迟的,只有在被真正使用时才会加载静态内部类。

主类被加载时,静态内部类不会被加载。

只有在调用静态内部类中的成员时,静态内部类才会被加载。

类加载的线程安全性:

JVM 在加载类时会自动保证线程安全。

静态变量 INSTANCE 只会被初始化一次。

(6) 枚举单例
java 复制代码
public enum Singleton {
    INSTANCE; // 枚举单例的唯一实例

    // 单例类的其他方法
    public void doSomething() {
        System.out.println("Singleton instance is working!");
    }
}

特点:

  • 线程安全: 枚举的线程安全由 JVM 保证。
  • 防反序列化破坏: 默认枚举实例不可反序列化。

优点:

  • 实现简单,天生线程安全。
  • 避免反序列化和反射破坏单例。

缺点:

  • 不支持延迟加载。
  • 某些场景下(如需要灵活实例化控制)不适用。
java 复制代码
public class Test {
    public static void main(String[] args) {
        Singleton instance = Singleton.INSTANCE;
        instance.doSomething();
    }
}

为什么枚举单例是线程安全的?

**JVM 保证:**枚举类型在类加载时会由 JVM 初始化,类加载过程是线程安全的。

**枚举的单一实例特性:**枚举中的每个实例在加载时被自动创建,并且不可被外部修改。

三、单例实现对比

实现方式 是否线程安全 是否延迟加载 性能 复杂度
饿汉模式 简单
懒汉模式(不安全) 简单
懒汉模式(同步) 低(频繁加锁) 简单
双重检查模式(DCL) 较高 中等
静态内部类模式 简单
枚举单例 简单
相关推荐
forestsea7 分钟前
【Java 解释器模式】实现高扩展性的医学专家诊断规则引擎
java·人工智能·设计模式·解释器模式
小白不太白9502 小时前
设计模式之 命令模式
设计模式·命令模式
小白不太白9503 小时前
设计模式之 备忘录模式
服务器·设计模式·备忘录模式
zzzhpzhpzzz3 小时前
设计模式——策略模式
设计模式·策略模式
入门到跑路3 小时前
【君正T31开发记录】8.了解rtsp协议及设计模式
网络协议·设计模式
小白不太白9503 小时前
设计模式之 解释器模式
java·设计模式·解释器模式
小白不太白9504 小时前
设计模式之 代理模式
设计模式·代理模式
吃汉堡吃到饱4 小时前
【创建型设计模式】工厂模式
设计模式
程序员与背包客_CoderZ4 小时前
C++设计模式——Singleton单例模式
c语言·开发语言·c++·单例模式·设计模式