单例模式与反射创建对象

单例模式

饿汉式单例模式

单例模式,就是自己先把自己创建了,整个程序都只有这一个实例,别人都没有办法创建实例,因为他的构造方法是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拿到反射对象,通过反射对象获取到构造方法,因为是无参,所以是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);
      }
      
    • 通过反射将其破坏了

  • 解决方式

    • 创建对象的时候,添加判断
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拿到反射对象,通过反射对象获取到构造方法,因为是无参,所以是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是会抛出异常的

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

    • 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)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/565012.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[lesson48]同名覆盖引发的问题

同名覆盖引发的问题 父子间的赋值兼容 子类对象可以当做父类对象使用(兼容性) 子类对象可以直接赋值给父类对象(<font color>兼容性)子类对象可以直接初始化父类对象父类指针可以直接指向子类对象父类引用可以直接引用子类对象 当使用父类指针(引用)指向子类对象时 子类…

安装zabbix server

目录 1、实验环境 2、yum 安装zabbix server 2.1 解决权限问题和放行流量 2.2 安装zabbix-server 1、实验环境 操作系统rhel8zabbix6.0TLS数据库mysql8.0.30IP地址192.168.81.131时间配置NTP时间服务器同步 2、yum 安装zabbix server 如果通过yum源安装&#xff0c;操作系…

《ElementUI 基础知识》png 图片扩展 icon用法

前言 UI 设计给的切图是 .png 格式。但想与 Element UI icon 用法类似&#xff0c;方案如下。 实现 步骤一 准备图片 步骤二 新建文件&#xff0c;可使用 CSS 预处理语言 styl 或 scss。 stylus 方式 文件 icon.styl /* 定义一个混合 */ cfgIcon(w, h) {display: inlin…

滑动窗口做题思路

什么是滑动窗口&#xff1f;就是一个队列,然后通过在这个队列中的各种移除和添加满足题目需求 题目: 209. 长度最小的子数组 - 力扣&#xff08;LeetCode&#xff09; class Solution {public int minSubArrayLen(int target, int[] nums) {int left 0;int sum 0;int n nu…

面向对象设计与分析40讲(25)中介模式、代理模式、门面模式、桥接模式、适配器模式

文章目录 门面模式代理模式中介模式 之所以把这几个模式放到一起写&#xff0c;是因为它们的界限比较模糊&#xff0c;结构上没有明显的差别&#xff0c;差别只是语义上。 这几种模式在结构上都类似&#xff1a; 代理将原本A–>C的直接调用变成&#xff1a; A–>B–>…

Java面试八股之marshalling和demarshalling

marshalling和demarshalling Marshalling&#xff08;序列化&#xff09;是将内存中的对象状态转化为适合传输或存储的格式&#xff08;如字节流、JSON、XML&#xff09;&#xff0c;以便进行网络通信、持久化存储或跨平台/语言交互操作。Demarshalling&#xff08;反序列化&a…

spring aop介绍

Spring AOP&#xff08;面向切面编程&#xff09;是一种编程范式&#xff0c;它允许开发者将横切关注点&#xff08;cross-cutting concerns&#xff09;从业务逻辑中分离出来&#xff0c;从而提高代码的模块化。在传统的对象导向编程中&#xff0c;这些横切关注点&#xff0c;…

【分治】Leetcode 颜色分类

题目讲解 75. 颜色分类 这道题的本质就是数组分三块 算法讲解 使用三个指针&#xff0c;i遍历数组&#xff0c;left标记0的最右侧&#xff0c;right标记2的最左侧 如果当前的nums[i] 0,我们就让nums[left] 和 nums[i]位置上的数字做交换&#xff0c;这里的i是可以向前移…

基于51单片机的宠物自动喂食语音播报,有实物

1. 51仿真&#xff1a; LCD第一屏显示食物重量&#xff0c;当前时间&#xff0c;温湿度。第二屏显示喂食时间&#xff0c;第三屏显示喂食重量。可通过点击查看喂食时间翻转屏幕显示。 点击查看喂食时间后&#xff0c;显示喂食时间&#xff0c;可以设置三个时间&#xff0c;再点…

【做一名健康的CSDNer】程序员哪几种行为最伤肾(程序员必看)

虽然没有专门针对程序员这一职业群体特有的伤肾行为的研究报道&#xff0c;但根据一般人群的健康风险和生活习惯&#xff0c;程序员由于其特殊的工作模式和环境&#xff0c;可能更容易出现如下伤肾的行为&#xff1a; 熬夜加班&#xff1a; 程序员由于项目进度、bug修复等原因&…

面试十八、容器适配器

容器适配器是一种特殊类型的容器&#xff0c;它们提供了一种不同于常规容器的接口和行为。容器适配器通常是建立在其他容器之上&#xff0c;通过改变接口或添加限制来满足特定的需求或解决特定的问题。 在 C 中&#xff0c;标准库提供了三种常见的容器适配器&#xff1a; 栈&am…

《HCIP-openEuler实验指导手册》1.3Apache动态功能模块加载卸载练习

1.3.1 配置思路 mod_status 模块可以帮助管理员通过web界面监控Apache运行状态&#xff0c;通过LoadModule指令加载该模块&#xff0c;再配置相关权限&#xff0c;并开启ExtendedStatus后&#xff0c;即可使用该模块。 1.3.2 配置步骤 检查mod_status模块状态&#xff08;使…

用户成功故事汇源达投顾选股软件见证智 慧投资的辉煌篇章

在投资领域&#xff0c;每一个成功的投资者背后&#xff0c;往往都有一款值得信赖的选股软件作为他们的得力助手。而河北源达的“财源滚滚”选股软件&#xff0c;正是这样一款备受投资者赞誉的智能投资工具。今天&#xff0c;我们就来分享一些使用财源滚滚选股软件获得成功的用…

【数学建模】虫子追击问题(仿真)

已知 有四个虫子,分别是 A , B , C , D A,B,C,D A,B,C,D A , B , C , D A,B,C,D A,B,C,D分别在 ( 0 , 0 ) , ( 0 , 1 ) , ( 1 , 1 ) , ( 1 , 0 ) (0,0),(0,1),(1,1),(1,0) (0,0),(0,1),(1,1),(1,0)四个虫子A追B&#xff0c;B追C&#xff0c;C追D&#xff0c;D追A四个速度相同 …

【UnityShader】图片圆角

1.需求 我们在开发的时候&#xff0c;有时候一些按钮或者菜单栏的边角是直角的需要改成圆角&#xff0c;但是让美术重新绘制耽误时间不说也确实没必要&#xff0c;这个时候我们不妨使用一个简单的shader去解决这个问题&#xff0c;下面我们就讲讲这个shader要如何实现。 需求1…

【智能算法】闪电搜索算法(LSA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2015年&#xff0c;H Shareef等人闪电自然现象启发&#xff0c;提出了闪电搜索算法&#xff08;Lightning Search Algorithm, LSA&#xff09;。 2.算法原理 2.1算法思想 LSA受到闪电梯级先导传…

c++取经之路(其七)——c++的内存管理new与delete

c的基本的内存管理一般就是用new和delete来管理 new与delete&#xff1a; 我们直接来将用法&#xff0c;我一般用new有两种用法&#xff0c;一种只申请一个这样的数据&#xff0c;另一种是申请多个这样的数据&#xff0c;比如我们要申请一个int&#xff0c;和申请十个int&…

MQTT服务器EMQX的安装和使用(Windows)

一、下载地址&#xff1a; 下载 EMQX 二、安装环境&#xff1a; Windows Server2016 16G 500G 三、启动服务&#xff1a; 下载文件解压后放入以下目录&#xff08;注意&#xff0c;目录名一定是英文&#xff0c;否则会造成启动不成功&#xff01;&#xff09;&#xff1a…

Linux实现标准输入和标准输出(STDIN_FILENO和STDOUT_FILENO)

在C语言中&#xff0c;scanf和printf函数用于标准输入和标准输出的输入输出操作。而在Linux中&#xff0c;STDIN_FILENO和STDOUT_FILENO是用于表示标准输入和标准输出的文件描述符。 STDIN_FILENO和STDOUT_FILENO是定义在头文件 <unistd.h> 中的常量&#xff0c;用于表示…

【C语言进阶】指针例题大杂烩,阁下是高手还是菜鸟?

前言 首先说明&#xff0c;本文不适合新手&#xff0c;如果你刚刚接触指针&#xff0c;可以看看前五点&#xff0c;这是我认为指针中比较重要的细节&#xff0c;例题部分酌情尝试。 如果你自认为指针学的不错&#xff0c;胸有成竹&#xff0c;请尝试最后的例题&#xff0c;如…