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

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

目录

概念

单例模式(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 自动处理,枚举单例在序列化时只会返回相同的枚举实例

总结

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

相关推荐
Lojarro3 分钟前
JavaEE基础之- Servlet相关
java·servlet·java-ee
KingDol_MIni26 分钟前
Spring Boot 集成 T-io 实现客户端服务器通信
java·服务器·spring boot
许苑向上30 分钟前
Java八股文(下)
java·开发语言
逸Y 仙X35 分钟前
Git常见命令--助力开发
java·大数据·git·java-ee·github·idea
菜鸟一枚在这35 分钟前
深入解析设计模式之单例模式
开发语言·javascript·单例模式
独孤求败Ace38 分钟前
第44天:Web开发-JavaEE应用&反射机制&类加载器&利用链&成员变量&构造方法&抽象方法
java·开发语言
FLZJ_KL38 分钟前
【设计模式】【创建型模式】单例模式(Singleton)
java·单例模式·设计模式
CL_IN1 小时前
企业数据集成:实现高效调拨出库自动化
java·前端·自动化
计算机-秋大田1 小时前
基于Spring Boot的农产品智慧物流系统设计与实现(LW+源码+讲解)
java·开发语言·spring boot·后端·spring·课程设计
计算机毕设指导61 小时前
基于SpringBoot的城乡商城协作系统【附源码】
java·spring boot·后端·mysql·spring·tomcat·maven