单例模式与反射创建对象

news/2024/9/23 20:22:04/

单例模式

饿汉式单例模式

单例模式,就是自己先把自己创建了,整个程序都只有这一个实例,别人都没有办法创建实例,因为他的构造方法是private的

  • 一次性把全部都创建了
public class HungryMan {private static int [][] s = new int[5][5];private static int [][] s1 = new int[5][5];private static int [][] s2 = new int[5][5];private static HungryMan hungryMan = new HungryMan();private HungryMan() {}private static HungryMan getInstance() {return hungryMan;}
}
  • 这样就会存在很多浪费空间

懒汉式单例模式

为了解决这种浪费,出现了懒汉式单例模式

  • 就是我什么都不创建,需要使用的时候再去创建

  • public class LazyMan {private static LazyMan lazyMan;private LazyMan() {}private static LazyMan getLazyMan(){if(lazyMan == null){lazyMan = new LazyMan();}return lazyMan;}
    }
    

单例模式的问题

  • 在单线程下,创建是没有问题的,但是在多线程下创建就会有问题
public class LazyMan {private static LazyMan lazyMan;private LazyMan() {System.out.println(Thread.currentThread().getName() );}private static LazyMan getLazyMan(){if(lazyMan == null){lazyMan = new LazyMan();}return lazyMan;}public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(()->{LazyMan.getLazyMan();}).start();}}
}
  • 这里注意几个点

    • 需要在构造方法中输出

    • 而不是在

      • new Thread(()->{System.out.println(LazyMan.getLazyMan());
        }).start();
        
      • 因为sout是加锁的,所以拿到都是同一个对象

    • 这样才能测试出结果

解决问题

  • 双重检测锁【DCL懒汉式】
    • 为什么需要二次判断
    • 因为第一个判断是在同步代码块外的,所以很多线程都会去判断
    • 如果没有第二个判断,可能存在有线程已经创建了实例,所以会创建出多个实例
    • 当然你直接将整个代码块全部锁上也是可以的
    • 这个代码也是不安全的
    • 因为创建对象也不是原子性操作
      • 分配空间
      • 执行构造方法,初始化对象
      • 把对象指向这个空间
    • 所以也是不安全的,会出现重排,导致不安全
    • 需要给对象加上volatile,防治重排
//双重检测锁,解决这个问题
private static LazyMan getLazyMan(){if(lazyMan == null){synchronized (LazyMan.class) {if(lazyMan == null){lazyMan = new LazyMan();}}}return lazyMan;
}
  • 双重检测锁的问题

    • public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {//通过构建实例,构建对象LazyMan lazyMan = LazyMan.getLazyMan();//getClass拿到反射对象,通过反射对象获取到构造方法,因为是无参,所以是nullConstructor<? extends LazyMan> constructor = lazyMan.getClass().getDeclaredConstructor(null);//将私有变量变为公有变量constructor.setAccessible(true);//创建实例LazyMan lazyMan1 = constructor.newInstance();//或者LazyMan lazyMan2 = new LazyMan();System.out.println(lazyMan);System.out.println(lazyMan1);System.out.println(lazyMan2);
      }
      
    • 通过反射将其破坏了

  • 解决方式

    • 创建对象的时候,添加判断
private LazyMan() {synchronized (LazyMan.class){if(lazyMan != null){throw new RuntimeException("实例已经被创建");}}
}
  • 出现新的问题
    • 之前通过实例.getClass(),拿到反射对象,现在直接LazyMan.Class拿到对象,也不new对象了,直接newInstance(),获得到对象,这样单例模式还是被破坏了
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {//通过构建实例,构建对象//LazyMan lazyMan = LazyMan.getLazyMan();//getClass拿到反射对象,通过反射对象获取到构造方法,因为是无参,所以是nullConstructor<? extends LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);//将私有变量变为公有变量constructor.setAccessible(true);//创建实例LazyMan lazyMan1 = constructor.newInstance();LazyMan lazyMan2 = constructor.newInstance();System.out.println(lazyMan1);System.out.println(lazyMan2);
}
  • 枚举类

    • 枚举类是没有不可以通过反射办法破环单例模式

    • 在这里插入图片描述

    • 可以看出如果获取枚举类的instance是会抛出异常的

    • 尝试破坏枚举类的单例模式

    • public enum LazyEnum {INSTANCE;}
      class Test{public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {LazyEnum instance = LazyEnum.INSTANCE;System.out.println(instance);Constructor<LazyEnum> declaredConstructor = LazyEnum.class.getDeclaredConstructor(String.class, int.class);declaredConstructor.setAccessible(true);LazyEnum lazyEnum = declaredConstructor.newInstance();System.out.println(lazyEnum);}
      }
      
    • 关于这个为什么是一个有参数的构造

      • 如何知道枚举类是有参构造还是无参构造

      • 在这里插入图片描述

      • 这里显示了是一个无参构造

      • 在这里插入图片描述

      • 对于字节码文件的查看,显示的也是无参构造

    • 如果是无参构造就会抛出NoSuchMethod的错误,表示没有这个方法

    • 需要使用别的方法去查看到底是有参数还是无参构造

      • 使用的软件是jad.exe
      • 将LazyEnum.class文件反编译成Lazy.java文件,再进去查看,这样我们可以得到一个含有有参构造的构造函数,参数分别是Sring 和 int(注意不是Integer)

http://www.ppmy.cn/news/1429230.html

相关文章

用Bash变量进行sed替换

问题&#xff1a; 我试图在一个 Bash 脚本中使用 sed 改变文本文件中的值&#xff1a; sed s/draw($old_num;n_)/draw($new_num;n_)/g file.txt > tmp这行代码将在 for 循环中。为什么它不起作用&#xff1f; 回答&#xff1a; 当使用 Bash 变量进行 sed 替换时&#xff…

【数据结构】冒泡排序

冒泡排序是一种简单的排序算法。 它基于重复地交换相邻元素的位置。算法的每一步都会比较相邻的两个元素&#xff0c;如果它们的顺序错误&#xff08;即第一个元素比第二个元素大&#xff09;&#xff0c;则交换它们。这样&#xff0c;每经过一轮比较和交换&#xff0c;数组中…

20240417金融读报:金融支持制造通知外汇局修订资本项目指引碳足迹现状与痛点

1、《关于深化制造业金融服务 助力推进新型工业化的通知》强化制造业中长期贷款和信用贷款支持&#xff0c;重点服务基础/创新行业&#xff0c;支持设备以旧换新&#xff08;更绿色、更智能&#xff09; 2、国家外汇局修订资本项目外汇业务指引&#xff0c;2024版5月6日起执行&…

【2024 SCI一区】 基于DCS-BiLSTM-Attention的多元回归预测(Matlab实现)

【2024 SCI一区】 基于DCS-BiLSTM-Attention的多元回归预测&#xff08;Matlab实现&#xff09; 目录 【2024 SCI一区】 基于DCS-BiLSTM-Attention的多元回归预测&#xff08;Matlab实现&#xff09;效果一览基本介绍程序设计参考资料 效果一览 基本介绍 差异创意搜索算法&…

dns可能不可用什么原因?

DNS&#xff08;Domain Name System&#xff09;可能不可用的原因有多种&#xff0c;包括&#xff1a; DNS服务器故障&#xff1a;DNS服务器可能发生故障&#xff0c;导致无法提供域名解析服务。这可能是由于硬件故障、软件错误、配置问题或网络问题引起的。 网络故障&#xf…

ASP.NET基于Web Mail收发系统设计与开发

摘 要 互联网络技术的不断发展&#xff0c;电子邮件服务已经成为人们基本的信息交互手段&#xff0c;也是网络服务中最早和最基本的服务之一。传统邮件系统大多是基于C/S结构&#xff0c;如Lotus notes、Microsoft Exchange Server等&#xff0c;这些邮件系统占用相对较多的服…

python实现视频剪辑

即刻关注&#xff0c;获取更多 实现目标 因上传某盘等文件大小限制&#xff0c;无法上传视频&#xff0c;故需要对视频进行压缩 参考资料 ffmpeg文档参考: https://ffmpeg.org/ffmpeg.html 依赖条件 已经安装好python3.11 &#xff0c;原则上更高版本也可以 安装 ffmpeg 依赖 p…

总分420+专业140+哈工大哈尔滨工业大学803信号与系统和数字逻辑电路考研电子信息与通信工程,真题,大纲,参考书。

考研复习一路走来&#xff0c;成绩还是令人满意&#xff0c;专业803信号和数电140&#xff0c;总分420&#xff0c;顺利上岸&#xff0c;总结一下自己这一年复习经历&#xff0c;希望大家可以所有参考&#xff0c;这一年复习跌跌拌拌&#xff0c;有时面对压力也会焦虑&#xff…