您的位置:首页 > 游戏 > 手游 > 沧州疫情最新情况_武汉自适应网站_百度非企推广开户_百度信息流推广和搜索推广

沧州疫情最新情况_武汉自适应网站_百度非企推广开户_百度信息流推广和搜索推广

2024/12/23 14:58:42 来源:https://blog.csdn.net/AIAIAIXIAO_/article/details/144518285  浏览:    关键词:沧州疫情最新情况_武汉自适应网站_百度非企推广开户_百度信息流推广和搜索推广
沧州疫情最新情况_武汉自适应网站_百度非企推广开户_百度信息流推广和搜索推广

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

目录

  • 设计模式——单例模式(饿汉式,懒汉式等)
    • 概念
    • 核心要点
    • 实现
      • 基础要点
      • 饿汉式
      • 懒汉式
      • 懒汉式(线程安全,双重检查锁定)
      • 静态内部类实现
      • 使用枚举实现
    • 总结

概念

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

核心要点

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

实现

基础要点

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

饿汉式

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

示例代码如下:

public class Singleton {// 静态变量在类加载时初始化private static final Singleton INSTANCE = new Singleton();// 私有化构造器,防止外部实例化private Singleton() {}// 提供全局访问点public static Singleton getInstance() {return INSTANCE;}
}
  • 优点:实现简单,线程安全。
  • 缺点:即使未使用该实例,也会初始化,占用内存。

懒汉式

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

示例代码如下:

public class Singleton {// 静态变量,延迟初始化private static Singleton instance;// 私有化构造器private Singleton() {}// 提供全局访问点public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}
  • 优点:按需创建,节省资源。
  • 缺点:在多线程环境下存在安全问题(多个线程可能同时创建实例)

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

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

改造后如下:

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),确保指令在多线程环境中按程序的逻辑顺序执行。它可以防止变量的赋值操作被重排序到不合适的位置。
这里举一个例子:
示例代码:

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 时,可能得到一个“未初始化完全”的对象。

静态内部类实现

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

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 环境中。枚举不仅能实现线程安全的单例,还能天然防止反射攻击序列化漏洞,是最简洁且安全的单例实现方式。

示例代码:

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

总结

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

image-20241216205221031

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com