当前位置: 首页> 文旅> 文化 > 大朗疫情最新情况今天_建工网校论坛_网络营销推广方案设计_电子商务网站建设方案

大朗疫情最新情况今天_建工网校论坛_网络营销推广方案设计_电子商务网站建设方案

时间:2025/7/11 17:51:43来源:https://blog.csdn.net/m0_73569492/article/details/146958328 浏览次数:0次
大朗疫情最新情况今天_建工网校论坛_网络营销推广方案设计_电子商务网站建设方案
视频链接: 设计模式|狂神说

单例模式是什么?

单例模式是确保一个类在整个应用程序中只有一个实例,并提供一个全局方法访问这个实例。

单例模式分为饿汉式和懒汉式。

在这里插入图片描述

饿汉式单例

饿汉式顾名思义就是,程序一启动就创建这个单例bean

// 饿汉式
public class Hungry {// 静态变量在类加载时就会为其赋值private final static Hungry HUNGRY = new Hungry();private Hungry() {}public static Hungry getInstance() {return HUNGRY;}
}

简单介绍一下实例化类机制:

类加载过程:

首先是new对象、访问静态成员或继承时触发类加载,类加载是通过双亲委派机制来加载的,首先是类加载器查找类的字节码文件,读入内存(加载),进行链接操作(验证检验字节码的合法性、准备为静态变量赋值、解析将符号引用转直接引用),执行类构造器方法为静态变量赋值。

实例化阶段:

为对象分配内存,将实例变量初始化为零值,设置对象头信息,调用构造方法为成员变量赋值,返回对象地址

懒汉式单例

懒汉式单例是在要用的时候再去创建单例bean。

初步代码:

// 懒汉式单例
public class Lazy {private static Lazy lazy;private Lazy() {System.out.println(Thread.currentThread().getName() + ":实例化");}public static Lazy getInstance() {if (lazy == null) {lazy =  new Lazy();}return lazy;}public static void main(String[] args) {for (int i = 0; i < 5; i ++) {new Thread(()->{Lazy instance = Lazy.getInstance();System.out.println(Thread.currentThread().getName() + ": " + instance);}, "t" + i).start();}}
}

Lazy.getInstance会触发对象实例化,第一次对象实例化时,会由类加载器加载这个类到内存中,然后到实例化阶段,会对类进行初始化,然后调用构造方法,当没有时,默认调用无参构造方法,那对于上述代码,按照预想的单例模式,应该对象只会实例化一次,也就是说,只会执行一次无参构造方法,我们来测试一下:

结果显示:

t0:实例化
t2:实例化
t4:实例化
t2: com.aof.singleton.Lazy@59a30351
t3: com.aof.singleton.Lazy@31d0f5c9
t0: com.aof.singleton.Lazy@31d0f5c9
t1: com.aof.singleton.Lazy@57f64e55
t4: com.aof.singleton.Lazy@57f64e55

很明显,对象实例化了多次,并且返回的对象也不是同一个实例,为什么?

—>这是因为多线程并发导致竞态条件(因为执行时序导致的并发安全问题),简单来说,就是有可能多个线程一起读到了lazy==null,然后觉得对象没有被实例化过,然后它们都跑去实例化了。

解决竞态条件一般选择加锁的方式:

   // 双重检测锁模式public static Lazy getInstance() {if (lazy == null) {synchronized (Lazy.class) {if (lazy == null) {lazy =  new Lazy();}}}return lazy;}

还有什么问题呢?联想一下JMM内存模型的三大特性,原子性、有序性、可见性

这里还有一个很关键的操作lazy = new Lazy();,在进行这一步操作时,在底层并不是一步实现的,是通过多条指令来完成的,也就是说这条指令不是原子性操作,而JVM中允许一定的指令重排序来提高性能

lazy = new Lazy();:

  1. 分配内存
  2. 执行构造方法
  3. 返回引用地址

假设指令重排序了,变成1->3->2,当执行到3时,已经返回结果给lazy这个单例bean了,但是实际上lazy还没有执行构造方法没有实例化,假如此刻有另外一个线程过来读取lazy,发现lazy不为空,指向一块地址,这个线程在使用这个lazy的时候可能会出现问题

假设线程A和线程B同时执行 getInstance() 方法:

1.线程A的执行流程:

  • 步骤1:分配内存空间。
  • 步骤3:将内存地址赋值给 lazy(此时对象未初始化)。
  • 步骤2:后续执行构造函数初始化对象(可能稍后完成)。

2.线程B的执行流程:
在线程A执行到步骤3后,线程B进入 getInstance():

  • 检查 lazy != null(此时 lazy 已指向分配的内存地址,但对象未初始化)。
  • 直接返回 lazy 引用。

问题:线程B拿到的 lazy 对象尚未完成初始化,导致后续操作可能访问到未初始化的成员变量或方法,引发异常。

这里采用volatile关键字解决这个问题,volatile关键字禁止指令重排并保证其可见性。

private static volatile Lazy lazy;

Java里面有一个很霸道的东西叫做反射,它可以忽视修饰符的限制,所以在这里就有一个问题了,可以通过反射拿到构造器方法,然后创建实例对象,怎么解决?

@Test
public void getLazyInstance() throws Exception {// 通过反射拿到无参构造器 new个对象Constructor<Lazy> constructor = Lazy.class.getDeclaredConstructor();constructor.setAccessible(true);Lazy instance1 = constructor.newInstance();Lazy instance2 = constructor.newInstance();System.out.println(instance1);System.out.println(instance2);
}

结果:

com.aof.singleton.Lazy@4dcbadb4
com.aof.singleton.Lazy@4e515669

可以想到在类中加一个字段表示是否创建了实例,通过反射创建实例是可行的,但是我们限制它只能创建一次,也就是说通过反射创建实例只有第一次能创建成功,后续创建都拒绝。

private static boolean FLAG = false;private Lazy() {synchronized (Lazy.class) {if (FLAG) {throw new RuntimeException("不要用反射");}FLAG = true;}
}

这其实又回到了原来的问题,应该反射很霸道,所以也可以通过反射修改flag的值,所以还是会有问题。

改进方案:第一次调用构造器时(无论是通过getInstance还是反射)就把当前的单例bean赋值为this

private Lazy() {synchronized (Lazy.class) {if (lazy != null) {throw new RuntimeException("禁止多次通过反射创建实例!!!");}lazy = this;}
}

完整代码实现:

// 懒汉式单例
public class Lazy {private static volatile Lazy lazy;private Lazy() {if (lazy == null) {synchronized (Lazy.class) {if (lazy != null) {throw new RuntimeException("禁止多次通过反射创建实例!!!");}lazy = this;}}}// 双重检测锁模式public static Lazy getInstance() {if (lazy == null) {synchronized (Lazy.class) {if (lazy == null) {lazy =  new Lazy();}}}return lazy;}
}

测试:

@Test
public void getLazyInstance() throws Exception {// 通过反射拿到无参构造器 new个对象Constructor<Lazy> constructor = Lazy.class.getDeclaredConstructor();constructor.setAccessible(true);// 通过反射创建对象实例Lazy instance1 = constructor.newInstance();Lazy instance2 = Lazy.getInstance();System.out.println(instance1);System.out.println(instance2);
}

结果:

com.aof.singleton.Lazy@4dcbadb4
com.aof.singleton.Lazy@4dcbadb4

静态内部类模式

静态内部类模式(Static Inner Class Pattern)是单例模式的一种实现方式,通过静态内部类实现延迟加载和线程安全,同时避免了传统懒汉式单例模式中使用synchronized关键字的性能开销。

(1) 延迟加载(Lazy Initialization)

  • 静态内部类在外部类加载时不会被初始化,只有当显式使用内部类时才会被加载。
  • 单例对象在静态内部类的static变量中初始化,因此首次调用getInstance()时才会创建实例。

(2) 线程安全

  • JVM类加载机制保证了类初始化的线程安全性:当多个线程同时访问getInstance()时,JVM会确保静态内部类Holder的初始化只执行一次,且所有线程看到的INSTANCE都是同一个实例。
public class StaticLazy {private StaticLazy() {if (LazyHolder.instance != null && LazyHolder.instance != this) {throw new RuntimeException("不要通过反射多次创建对象");}}private static class LazyHolder {private static final StaticLazy instance = new StaticLazy();}public static StaticLazy getInstance() {return LazyHolder.instance;}
}

测试:

@Test
public void getSStaticLazyInstance() {for (int i = 0; i < 10; i ++) {new Thread(()->{StaticLazy instance = StaticLazy.getInstance();System.out.println(instance);}).start();}
}
@Test
public void getSStaticLazyInstanceByReflect() throws Exception{Constructor<StaticLazy> declaredConstructor = StaticLazy.class.getDeclaredConstructor();declaredConstructor.setAccessible(true);StaticLazy instance = declaredConstructor.newInstance();StaticLazy instance1 = declaredConstructor.newInstance();System.out.println(instance);System.out.println(instance1);
}

结果:

com.aof.singleton.StaticLazy@36e5bf5b
com.aof.singleton.StaticLazy@36e5bf5b
com.aof.singleton.StaticLazy@36e5bf5b
com.aof.singleton.StaticLazy@36e5bf5b
com.aof.singleton.StaticLazy@36e5bf5b
com.aof.singleton.StaticLazy@36e5bf5b
com.aof.singleton.StaticLazy@36e5bf5b
com.aof.singleton.StaticLazy@36e5bf5b
com.aof.singleton.StaticLazy@36e5bf5b
com.aof.singleton.StaticLazy@36e5bf5b

在这里插入图片描述

枚举类型

使用枚举类型的单例是最简洁的方式,不仅能避免多线程同步问题,还能防止反序列化重新创建新的对象。

public enum EnumSingleton {INSTANCE;private EnumSingleton() {}public static EnumSingleton getInstance() {return INSTANCE;}
}

测试:

@Test
public void getInstance() {EnumSingleton instance = EnumSingleton.getInstance();EnumSingleton instance1 = EnumSingleton.getInstance();EnumSingleton instance2 = EnumSingleton.INSTANCE;System.out.println(instance.hashCode());System.out.println(instance1.hashCode());System.out.println(instance2.hashCode());
}

结果:

1305193908
1305193908
1305193908

如有错误,欢迎指正!!!

关键字:大朗疫情最新情况今天_建工网校论坛_网络营销推广方案设计_电子商务网站建设方案

版权声明:

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

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

责任编辑: