单例模式与反射创建对象

单例模式

饿汉式单例模式

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

  • 一次性把全部都创建了
java 复制代码
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;
    }
}
  • 这样就会存在很多浪费空间

懒汉式单例模式

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

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

java 复制代码
public class LazyMan {
    private static LazyMan lazyMan;
    private LazyMan() {
        
    }
    private static LazyMan getLazyMan(){
        if(lazyMan == null){
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
}

单例模式的问题

  • 在单线程下,创建是没有问题的,但是在多线程下创建就会有问题
java 复制代码
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();
        }
    }
}
  • 这里注意几个点

    • 需要在构造方法中输出

    • 而不是在

      java 复制代码
      new Thread(()->{
          System.out.println(LazyMan.getLazyMan());
      }).start();
      • 因为sout是加锁的,所以拿到都是同一个对象
    • 这样才能测试出结果

解决问题

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

        }
    }
    return lazyMan;
}
  • 双重检测锁的问题

    java 复制代码
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //通过构建实例,构建对象
        LazyMan lazyMan = LazyMan.getLazyMan();
        //getClass拿到反射对象,通过反射对象获取到构造方法,因为是无参,所以是null
        Constructor<? 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);
    }
    • 通过反射将其破坏了
  • 解决方式

    • 创建对象的时候,添加判断
java 复制代码
private LazyMan() {
    synchronized (LazyMan.class){
        if(lazyMan != null){
            throw new RuntimeException("实例已经被创建");
        }
    }
}
  • 出现新的问题
    • 之前通过实例.getClass(),拿到反射对象,现在直接LazyMan.Class拿到对象,也不new对象了,直接newInstance(),获得到对象,这样单例模式还是被破坏了
java 复制代码
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    //通过构建实例,构建对象
    //LazyMan lazyMan = LazyMan.getLazyMan();
    //getClass拿到反射对象,通过反射对象获取到构造方法,因为是无参,所以是null
    Constructor<? 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是会抛出异常的

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

    java 复制代码
    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)
相关推荐
艾莉丝努力练剑2 小时前
【LeetCode&数据结构】单链表的应用——反转链表问题、链表的中间节点问题详解
c语言·开发语言·数据结构·学习·算法·leetcode·链表
倔强青铜36 小时前
苦练Python第18天:Python异常处理锦囊
开发语言·python
u_topian7 小时前
【个人笔记】Qt使用的一些易错问题
开发语言·笔记·qt
珊瑚里的鱼7 小时前
LeetCode 692题解 | 前K个高频单词
开发语言·c++·算法·leetcode·职场和发展·学习方法
AI+程序员在路上7 小时前
QTextCodec的功能及其在Qt5及Qt6中的演变
开发语言·c++·qt
xingshanchang7 小时前
Matlab的命令行窗口内容的记录-利用diary记录日志/保存命令窗口输出
开发语言·matlab
Risehuxyc7 小时前
C++卸载了会影响电脑正常使用吗?解析C++运行库的作用与卸载后果
开发语言·c++
AI视觉网奇8 小时前
git 访问 github
运维·开发语言·docker
不知道叫什么呀8 小时前
【C】vector和array的区别
java·c语言·开发语言·aigc
liulilittle8 小时前
.NET ExpandoObject 技术原理解析
开发语言·网络·windows·c#·.net·net·动态编程