• 单例模式与反射创建对象


    单例模式

    饿汉式单例模式

    单例模式,就是自己先把自己创建了,整个程序都只有这一个实例,别人都没有办法创建实例,因为他的构造方法是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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 这样就会存在很多浪费空间

    懒汉式单例模式

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

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

    • public class LazyMan {
          private static LazyMan lazyMan;
          private LazyMan() {
              
          }
          private static LazyMan getLazyMan(){
              if(lazyMan == null){
                  lazyMan = new LazyMan();
              }
              return lazyMan;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

    单例模式的问题

    • 在单线程下,创建是没有问题的,但是在多线程下创建就会有问题
    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();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 这里注意几个点

      • 需要在构造方法中输出

      • 而不是在

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

      • 这样才能测试出结果

    解决问题

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

      • 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);
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
      • 通过反射将其破坏了

    • 解决方式

      • 创建对象的时候,添加判断
    private LazyMan() {
        synchronized (LazyMan.class){
            if(lazyMan != null){
                throw new RuntimeException("实例已经被创建");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 出现新的问题
      • 之前通过实例.getClass(),拿到反射对象,现在直接LazyMan.Class拿到对象,也不new对象了,直接newInstance(),获得到对象,这样单例模式还是被破坏了
    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 枚举类

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

      • 在这里插入图片描述

      • 可以看出如果获取枚举类的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);
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
      • 关于这个为什么是一个有参数的构造

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

        • 在这里插入图片描述

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

        • 在这里插入图片描述

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

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

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

        • 使用的软件是jad.exe
        • 将LazyEnum.class文件反编译成Lazy.java文件,再进去查看,这样我们可以得到一个含有有参构造的构造函数,参数分别是Sring 和 int(注意不是Integer)
  • 相关阅读:
    从0开始刷力扣
    Spring 配置
    想定制Android系统实现改机?看完我也会了
    nginx空字节漏洞复现
    随机数实现
    【四】设计模式~~~创建型模式~~~建造者模式(Java)
    Seldom3.0: 支持App测试
    [附源码]计算机毕业设计springboot医学图像管理平台
    【datawhale202206】pyTorch推荐系统:多任务学习 ESMM&MMOE
    项目管理(如何进行项目质量管理)
  • 原文地址:https://blog.csdn.net/qq_64420904/article/details/138033065