设计模式——单例模式(饿汉式,懒汉式等)

设计模式------单例模式(饿汉式,懒汉式等)

目录

概念

单例模式(Singleton Pattern) 是一种创建型设计模式,它的目的是确保一个类只有一个实例,并提供一个全局访问点来访问该实例

核心要点

  • 唯一性
    类只有一个实例,避免重复创建多个对象导致资源浪费或状态不一致。
  • 全局访问点
    提供一个全局方法,方便访问唯一实例。

实现

基础要点

根据单例模式的特点,要求实现单例模式必须要构造器私有化,防止外界创建对象

饿汉式

顾名思义,该方式就是直接在类加载时就初始化单例实例

示例代码如下:

java 复制代码
public class Singleton {
    // 静态变量在类加载时初始化
    private static final Singleton INSTANCE = new Singleton();

    // 私有化构造器,防止外部实例化
    private Singleton() {}

    // 提供全局访问点
    public static Singleton getInstance() {
        return INSTANCE;
    }
}
  • 优点:实现简单,线程安全。
  • 缺点:即使未使用该实例,也会初始化,占用内存。

懒汉式

也是顾名思义,就是"懒",即在类加载时并不会立即创建实例,而是在外界需要时才会创建实例

示例代码如下:

java 复制代码
public class Singleton {
    // 静态变量,延迟初始化
    private static Singleton instance;

    // 私有化构造器
    private Singleton() {}

    // 提供全局访问点
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
  • 优点:按需创建,节省资源。
  • 缺点:在多线程环境下存在安全问题(多个线程可能同时创建实例)

懒汉式(线程安全,双重检查锁定)

由于在高并发情况下,一般的懒汉式方案可能会出现线程安全问题,所以需要对其进行改造

改造后如下:

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 关键字:

volatile 是 Java 提供的一个关键字,用于修饰变量,确保变量在多线程环境中的可见性禁止指令重排序。这些特性是为了解决并发编程中的常见问题,如线程间数据不一致和由于优化导致的潜在问题。

  • 可见性

在多线程环境中,每个线程都有自己的工作内存(CPU 缓存),线程可能会将变量的副本缓存在自己的工作内存中,导致一个线程对变量的修改对另一个线程不可见。而使用volatile 修饰的变量,所有线程对其的读写操作直接发生在主内存中。当一个线程修改了 volatile 变量的值,其他线程能够立即看到最新值。

  • 禁止指令重排序

为了优化性能,CPU 和编译器可能会对指令进行重排序(Instruction Reordering)。在单线程环境下,这种重排序不会改变程序的执行结果,但在多线程环境下,可能导致意想不到的问题,所以也得注意一下。volatile 通过加入内存屏障(Memory Barrier),确保指令在多线程环境中按程序的逻辑顺序执行。它可以防止变量的赋值操作被重排序到不合适的位置。

这里举一个例子:

示例代码:

java 复制代码
class Singleton {
    private static Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {                   // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) {           // 第二次检查
                    instance = new Singleton();   // 问题出在这里
                }
            }
        }
        return instance;
    }
}

这里看到 instance 没有被 volatile修饰,那么就可能会出现问题:

instance = new Singleton() 是一个分解过程,可能会被重排序成以下步骤:

  1. 分配内存空间。
  2. 将内存地址赋值给 instance
  3. 调用构造函数初始化对象。

如果发生了重排序,步骤可能变成:

  • 先执行第 2 步,再执行第 3 步

此时,另一个线程读取 instance 时,可能得到一个"未初始化完全"的对象。

静态内部类实现

利用类加载机制实现线程安全,推荐使用

java 复制代码
public class Singleton {
    // 静态内部类
    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton() {}

    // 通过静态内部类返回实例
    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

优点

  • 线程安全
  • 延迟加载
  • 实现简单

简单解读一下:

在外部类加载时,并不会主动加载内部类,而只有当外界主动使用内部类时,内部类才会被类加载器加载。借助这一特性可以实现延迟加载。并且,当类加载加载内部类时,由 JVM 保证线程的安全性,这样可以实现线程安全。总的来看,操作还是十分简单的。

使用枚举实现

使用 枚举(Enum) 是单例模式的一种推荐实现方式,尤其在 Java 环境中。枚举不仅能实现线程安全的单例,还能天然防止反射攻击序列化漏洞,是最简洁且安全的单例实现方式。

示例代码:

java 复制代码
public enum Singleton {
    INSTANCE; // 枚举类型的唯一实例

    // 示例方法
    public void doSomething() {
        System.out.println("Executing some logic in Singleton");
    }
}

优点

  • 简洁
  • 天然线程安全
  • 防止反射和序列化攻击

简单解读一下:

在 Java 中,每个枚举类型都是通过 java.lang.Enum 类实现的。枚举类的实例化是由 JVM 保证的,并且其类加载机制确保了线程安全性。每个枚举常量(如 INSTANCE)在类加载时就会被初始化(饿汉式 ),且只会初始化一次。

通过反射可以破坏普通单例模式,调用私有构造器来创建新的实例。但枚举类型的构造器在 Java 中是隐式的,JVM 不允许通过反射调用枚举类型的构造器,因此无法创建多个实例。

普通单例在序列化和反序列化时可能生成多个实例(通过 ObjectInputStream)。枚举类型的序列化由 JVM 自动处理,枚举单例在序列化时只会返回相同的枚举实例

总结

多种实现方案的比较如下:

相关推荐
程序员清风13 分钟前
专业再升级!程序员专属显示器明基RD280UG上手实测!
java·后端·面试
Lam Tang2 小时前
APS 系列文章 08
java·代理模式
萧瑟余晖3 小时前
Java深入解析篇三十三之消息队列
java·开发语言
码匠许师傅3 小时前
【C++ 面试真题】聊聊 C++ 的序列容器
java·c++·面试
IT爱学堂3 小时前
尚硅谷Java+AI大模型应用开发革新版本 2025年3月
java·开发语言·人工智能
不爱编程的小九九3 小时前
小九源码-springboot004-springboot智能阅读推荐系统
java·spring boot·后端
SCandL1524 小时前
Docker的实战应用
java·docker·容器
AC赳赳老秦4 小时前
OpenClaw 多源采集公开行业数据:从原始信息到研究报告初稿的自动化实践
java·c语言·c++·python·php·deepseek·openclaw
Java成神之路-5 小时前
深入理解三大高频队列:ArrayDeque、LinkedList、PriorityQueue 方法、特性与场景选择
java
roman_日积跬步-终至千里5 小时前
【资源控制】自助查询的智能路由
java·大数据·数据库