当前位置: 首页 > news >正文

手撕设计模式——计划生育之单例模式

1.业务需求

​ 大家好,我是菠菜啊。80、90后还记得计划生育这个国策吗?估计同龄的小伙伴们,小时候常常被”只生一个好“”少生、优生“等宣传标语洗脑,如今国家已经放开并鼓励生育了。话说回来,现实生活中有计划生育,你知道设计模式中也有计划生育吗?它是怎么实现的?

在这里插入图片描述

2.代码实现

我们只要保证一个类只有一个实例化对象,这样就能达到计划生育的目的。其实这个设计模式大家应该都很熟悉了,叫做单例模式

实现思路

类的实例化交给类本身,对外提供一个访问该单例的全局访问点,重点考虑线程安全、系统资源消耗、反射以及反序列化破坏等因素。

2.1 静态代码块

public  class StaticBlockSingleton {private static StaticBlockSingleton singleton;static {singleton=new StaticBlockSingleton();}private StaticBlockSingleton(){}public static StaticBlockSingleton getInstance(){return singleton;}
}

2.2 饿汉式

public  class HungrySingleton  {private static HungrySingleton singleton=new HungrySingleton();private HungrySingleton(){}public static HungrySingleton getInstance(){return singleton;}
}

思考:静态代码块和饿汉式不管singleton对象有没有被使用,都会在系统初始化的时候初始化对象从而占用系统资源

2.3 懒汉式

public  class LazySingleton implements Serializable {//volatile 防止指令重排private static volatile LazySingleton singleton;private LazySingleton(){}public static LazySingleton getInstance(){//1.第一层检索 提高执行效率 如果不是null 直接返回if(null==singleton){//2.多个线程同时进入 获取锁的执行,没有获取锁的等待synchronized (LazySingleton.class){//3.防止2步骤有等待锁的线程 锁释放后拿到锁后需判断一下对象是否创建 if (null==singleton){singleton=new LazySingleton();}}}return singleton;}
}

思考:**双重检查锁定(Double-Check Locking)**懒汉式让对象实例化延迟加载,减少了对象未被使用而占用系统资源,但是引入了锁,系统性能有一定影响。volatile关键字会屏蔽Java虚拟机所做的一些代码优化,也可能会导致系统运行效率降低。

拓展:指令重排

问题:

​ 为什么DCL实现单例,还需要用volatile修饰实例呢?

分析:

​ 问题出现在‘singleton=new LazySingleton();’这行代码,java创建对象不是一个原子操作,可以被分解为3步:

//1.分配对象的内存空间
//2.初始化对象
//3.将instance指向刚分配的内存地址

编译器或者处理器在执行代码的时候为了最大地提高性能,可能会将执行执行顺序重排,2和3执行顺序可能是相反的。在单线程情况下,重排序没有什么问题,因为他最终结果都是一致的。但是如果在多线程并发下,就会有可能有问题了。下面模拟俩个线程创建单例的场景:

CPU时间片线程A线程B
T1A-1:分配singleton对象的内存空间
T2A-3:将instance指向刚分配的内存地址B-1:第一层判断instance是否为null
T3B-2:instance不为null,B线程获得instance引用的对象
T4A-2:初始化对象
T5A-4:A线程获得instance引用的对象

在这里插入图片描述

​ 如果按照上面的顺序,B线程获取到的是一个未初始化的对象,这就有问题了。解决方案就是引入volatile关键字,它有俩个作用:一是保证变量的内存可见性,二是禁止指令重排。

保证变量内存可见性:

如果属性被volatile修饰,相当于会告诉CPU,对当前属性的操作,不允许使用CPU的缓存,必须去和主内存操作。

volatile的内存语义:

  • volatile属性被写:当写一个volatile变量,JMM会将当前线程对应的CPU缓存及时的刷新到主内存中
  • volatile属性被读:当读一个volatile变量,JMM会将对应的CPU缓存中的内存设置为无效,必须去主内存中重新读取共享变量

禁止指令重排:

当我们使用 volatile 关键字来修饰一个变量时,Java 内存模型会插入内存屏障(一个处理器指令,可以对 CPU 或编译器重排序做出约束)来确保以下两点:

  • 写屏障(Write Barrier):当一个 volatile 变量被写入时,写屏障确保在该屏障之前的所有变量的写入操作都提交到主内存。
  • 读屏障(Read Barrier):当读取一个 volatile 变量时,读屏障确保在该屏障之后的所有读操作都从主内存中读取。

2.4 静态内部类

public class StaticInnerSingleton implements Serializable {private static class InnerSingleton{private static final StaticInnerSingleton instance = new StaticInnerSingleton();}private StaticInnerSingleton(){}public static StaticInnerSingleton getInstance(){return InnerSingleton.instance;}}

思考:静态instance不是StaticInnerSingleton类的成员变量,所以在类加载的时候不会实例化instance,当第一次调用getInstance方法时,内部类InnerSingleton类会初始化instance,JVM保证其线程安全性,确保该成员变量只初始化一次。既实现了延迟加载,又没有性能消耗所以静态内部类这种方式比较推荐。但是有反射和反序列化破坏问题

2.5 枚举

public enum  Singleton implements Serializable {INSTANCE;void doSomething(){System.out.println("do something");}}

思考:枚举可以天然的防止反射和反序列化,但是不能延迟加载,这种方式是《Effective Java》作者的Josh Bloch提倡的方式。

拓展:反射和反序列化破坏单例

  • 反射破坏单例

    破坏案例:

    以DCL为例,反射生成俩个对象。

    public class Client {public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {//获取类的构造器Constructor<LazySingleton> constructor = LazySingleton.class.getDeclaredConstructor();//设置权限constructor.setAccessible(true);//使用 constructor 创造对象LazySingleton obj1 = constructor.newInstance();LazySingleton obj2 = constructor.newInstance();System.out.println(obj1);System.out.println(obj2);}
    }
    

    运行结果

    打印俩次对象的地址不一样,说明俩个对象不是同一个。

    在这里插入图片描述

    预防措施:

    可以在构造方法中抛出异常

    public  class LazySingleton implements Serializable {//volatile 防止指令重排private static volatile LazySingleton singleton;private LazySingleton(){if(singleton!=null){throw new RuntimeException("不允许重复创建对象!");}}}
    

    枚举预防源码:

    newInstance方法单独判断是否是枚举类型,如果是的话抛出异常,防止反射破坏单例模式。

    在这里插入图片描述

  • 反序列化破坏单例

    破坏案例:

    以DCL为例,反序列化生成俩个对象。

    public class Client2 {public static void main(String[] args) throws Exception {LazySingleton hungrySingleton = LazySingleton.getInstance();System.out.println(hungrySingleton);//将得到的实例序列化到磁盘FileOutputStream fileOutputStream = new FileOutputStream("D:/LazySingleton.txt");ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);objectOutputStream.writeObject(hungrySingleton);objectOutputStream.flush();objectOutputStream.close();//从磁盘反序列化得到实例FileInputStream fileInputStream = new FileInputStream("D:/LazySingleton.txt");ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);LazySingleton singleton = (LazySingleton) objectInputStream.readObject();System.out.println(singleton);}
    }
    

    运行结果

    打印俩次对象的地址不一样,说明俩个对象不是同一个。

    在这里插入图片描述

    原因分析

    obj = desc.isInstantiable() ? desc.newInstance() : null; 这行代码的意思是:如果这个类可以序列化,就创建新对象,不行就返回null。

    在这里插入图片描述

    预防措施

    添加readResolve(),返回单例对象。当反序列化恢复一个新对象时,系统会自动调用这个readResolve()方法返回指定好的对象。

    public  class LazySingleton implements Serializable {//volatile 防止指令重排private static volatile LazySingleton singleton;private Object readResolve(){return singleton;}
    }
    

    枚举预防源码:

    readEnum方法中‘Enum<?> en = Enum.valueOf((Class)cl, name);’这行代码,等提供于将instance对象赋值给en,枚举做了特殊处理,防止反序列化破坏单例模式。

    在这里插入图片描述

3.定义以及实现步骤

单例模式(Singleton)指一个类只有一个实例,且该类能自行创建这个实例的一种模式。

在这里插入图片描述

​ 通用实现步骤:

  1. 私有化构造方法
  2. 在单例内部创建一个唯一实例
  3. 提供一个外部获取实例的方法

4.优缺点以及应用场景

优点:

  • 提供了唯一实例的全局访问方法,可以优化共享资源的访问
  • 避免对象的频繁创建和销毁,可以提高性能

缺点:

  • 单例模式的代码基本上在一个类中,违反了单一职责
  • 单例模式不易扩展,扩展需要修改原来的代码,违背开闭原则

适用场景:

  • 创建一个对象资源消耗过高,并且只需一个
  • 只允许使用一个公共访问点

现实应用场景:

  • 数据库连接池
  • 手机app窗口(大多数app)
  • Spring中Bean的默认生命周期
  • Windows任务管理器

你的收藏和点赞就是我最大的创作动力,关注我我会持续输出更新!

友情提示:请尊重作者劳动成果,如需转载本博客文章请注明出处!谢谢合作!

【作者:我爱吃菠菜 】
在这里插入图片描述

相关文章:

手撕设计模式——计划生育之单例模式

1.业务需求 ​ 大家好&#xff0c;我是菠菜啊。80、90后还记得计划生育这个国策吗&#xff1f;估计同龄的小伙伴们&#xff0c;小时候常常被”只生一个好“”少生、优生“等宣传标语洗脑&#xff0c;如今国家已经放开并鼓励生育了。话说回来&#xff0c;现实生活中有计划生育&…...

Mac M3 Pro 部署Flink-1.16.3

目录 1、下载安装包 2、解压及配置 3、启动&测试 4、测试FlinkSQL读取hive数据 以上是mac硬件配置 1、下载安装包 官网&#xff1a;Downloads | Apache Flink 网盘&#xff1a; Flink 安装包 https://pan.baidu.com/s/1IN62_T5JUrnYUycYMwsQqQ?pwdgk4e Flink 已…...

Mysql 的分布式策略

1. 前言 MySQL 作为最最常用的数据库&#xff0c;了解 Mysql 的分布式策略对于掌握 MySQL 的高性能使用方法和更安全的储存方式有非常重要的作用。 它同时也是面试中最最常问的考点&#xff0c;我们这里就简单总结下 Mysq 的常用分布式策略。 2. 复制 复制主要有主主复制和…...

记录一个利用winhex进行图片隐写分离的

前提 是一次大比武里面的题目&#xff0c;属实给我开了眼&#xff0c;跟我之前掌握的关于隐写合并的操作都不一样。 它不是直接在文件里面进行输入文件隐写&#xff0c;叫你输入密码&#xff0c;或者更改颜色&#xff0c;或者偏移位置&#xff1b; 它不是单纯几个文件合并&a…...

压缩映射定理证明

收缩映射定理&#xff08;又称Banach不动点定理&#xff09;是一个重要的结果&#xff0c;特别是在分析和应用数学中。 定理&#xff08;收缩映射定理&#xff09;&#xff1a;假设是一个从度量空间 (X,d) 到自身的函数&#xff0c;如果 是一个收缩映射&#xff0c;即存在常数 …...

Ubuntu20.04.6操作系统安装教程

一、VMware Workstation16安装 选择安装VMware Workstation&#xff0c;登录其官网下载安装包&#xff0c;链接如下&#xff1a; 下载 VMware Workstation Pro 下载后运行安装向导&#xff0c;一直Next即可。 二、Ubuntu镜像下载 ubuntu20.04 选择需要下载的镜像类型下载即…...

(分治算法3)leecode 53 最大子数组和(最大子段和)

题目描述 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。 子数组是数组中的一个连续部分。 分治解法 这个问题可以分成从左半边数组找最大子段和从右半部分找最大子段和…...

【C++】模板初级

【C】模板初级 泛型编程函数模板函数模板的概念函数模板格式函数模板的原理函数模板的实例化模板参数的匹配原则 类模板类模板格式类模板的实例化 泛型编程 当我们之前了解过函数重载后可以知道&#xff0c;一个程序可以出现同名函数&#xff0c;但参数类型不同。 //整型 voi…...

eslint 使用单引号,Prettier使用双引号冲突

当 ESLint 规则要求使用单引号 (quotes: single) 而 Prettier 默认使用双引号时&#xff0c;会发生配置冲突。为了解决这个问题&#xff0c;你需要统一这两个工具的配置&#xff0c;确保它们遵循相同的规则。这里推荐两种解决方案&#xff1a; 解决方案 1: 修改 ESLint 配置以…...

进化生物学的数学原理 知识点总结

1、进化论与自然选择 1.1 进化论 1、进化论 过度繁殖 -> 生存竞争 -> 遗传和变异 -> 适者生存 2、用进废退学说与自然选择理论 用进废退&#xff1a;一步适应&#xff1a;变异 适应 自然选择&#xff1a;两步适应&#xff1a;变异 选择 适应 3、木村资生的中性…...

如何挑到高质量的静态IP代理?

在数字化时代&#xff0c;静态住宅IP代理已成为网络活动中不可或缺的一部分。无论是数据采集、网站访问&#xff0c;还是其他需要隐藏真实IP地址的在线活动&#xff0c;高质量的静态住宅IP代理都发挥着至关重要的作用。今天IPIDEA代理IP将详细介绍如何获取高质量的静态住宅IP代…...

vagrant putty错误的解决

使用Vagrant projects for Oracle products and other examples 新创建的虚机&#xff0c;例如vagrant-projects/OracleLinux/8。 用vagrant ssh可以登录&#xff1a; $ vagrant ssh > vagrant: Getting Proxy Configuration from Host...Welcome to Oracle Linux Server …...

图像分割——U-Net论文介绍+代码(PyTorch)

0、概要 原理大致介绍了一下&#xff0c;后续会不断精进改的更加详细&#xff0c;然后就是代码可以对自己的数据集进行一个训练&#xff0c;还会不断完善&#xff0c;相应其他代码可以私信我。 一、论文内容总结 摘要&#xff1a;人们普遍认为&#xff0c;深度网络成功需要数…...

C#进阶-ASP.NET的WebService跨域CORS问题解决方案

在现代的Web应用程序开发中&#xff0c;跨域资源共享&#xff08;Cross-Origin Resource Sharing, CORS&#xff09;问题是开发者经常遇到的一个挑战。特别是当前端和后端服务部署在不同的域名或端口时&#xff0c;CORS问题就会显得尤为突出。在这篇博客中&#xff0c;我们将深…...

如何利用TikTok矩阵源码实现自动定时发布和高效多账号管理

在如今社交媒体的盛行下&#xff0c;TikTok已成为全球范围内最受欢迎的短视频平台之一。对于那些希望提高效率的内容创作者而言&#xff0c;手动发布和管理多个TikTok账号可能会是一项繁琐且耗时的任务。幸运的是&#xff0c;通过利用TikTok矩阵源码&#xff0c;我们可以实现自…...

Java高级编程技术详解:从多线程到算法优化的全面指南

复杂度与优化 复杂度与优化在算法中的应用 算法复杂度是衡量算法效率的重要指标。了解和优化算法复杂度对提升程序性能非常关键。本文将介绍时间复杂度和空间复杂度的基本概念&#xff0c;并探讨一些优化技术。 时间复杂度和空间复杂度 时间复杂度表示算法执行所需时间随输…...

Redis 分布式锁过期了,还没处理完怎么办?

为了防止死锁&#xff0c;我们会给分布式锁加一个过期时间&#xff0c;但是万一这个时间到了&#xff0c;我们业务逻辑还没处理完&#xff0c;怎么办&#xff1f; 这是一个分布式应用里很常见到的需求&#xff0c;关于这个问题&#xff0c;有经验的程序员会怎么处理呢&#xff…...

Vue2+Element-ui后台系统常用js方法

el-dialog弹框关闭清空form表单并清空验证 cancelDialog(diaLog, formRef) {this[diaLog] falseif (formRef) {this.$refs[formRef].resetFields()} }页面使用&#xff1a; <el-dialog :visible.sync"addSubsidyDialog.dialog" close"cancelDialog(addSub…...

Kafka高频面试题整理

文章目录 1、什么是Kafka?2、kafka基本概念3、工作流程4、Kafka的数据模型与消息存储机制1)索引文件2)数据文件 5、ACKS 机制6、生产者重试机制:7、kafka是pull还是push8、kafka高性能高吞吐的原因1&#xff09;磁盘顺序读写&#xff1a;保证了消息的堆积2&#xff09;零拷贝机…...

uniapp地图自定义文字和图标

这是我的结构&#xff1a; <map classmap id"map" :latitude"latitude" :longitude"longitude" markertap"handleMarkerClick" :show-location"true" :markers"covers" /> 记住别忘了在data中定义变量…...

如何为服务器生成TLS证书

TLS&#xff08;Transport Layer Security&#xff09;证书是确保网络通信安全的重要手段&#xff0c;它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书&#xff0c;可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

【git】把本地更改提交远程新分支feature_g

创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?

在大数据处理领域&#xff0c;Hive 作为 Hadoop 生态中重要的数据仓库工具&#xff0c;其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式&#xff0c;很多开发者常常陷入选择困境。本文将从底…...

管理学院权限管理系统开发总结

文章目录 &#x1f393; 管理学院权限管理系统开发总结 - 现代化Web应用实践之路&#x1f4dd; 项目概述&#x1f3d7;️ 技术架构设计后端技术栈前端技术栈 &#x1f4a1; 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 &#x1f5c4;️ 数据库设…...

用机器学习破解新能源领域的“弃风”难题

音乐发烧友深有体会&#xff0c;玩音乐的本质就是玩电网。火电声音偏暖&#xff0c;水电偏冷&#xff0c;风电偏空旷。至于太阳能发的电&#xff0c;则略显朦胧和单薄。 不知你是否有感觉&#xff0c;近两年家里的音响声音越来越冷&#xff0c;听起来越来越单薄&#xff1f; —…...

算法岗面试经验分享-大模型篇

文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer &#xff08;1&#xff09;资源 论文&a…...

【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看

文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...

手机平板能效生态设计指令EU 2023/1670标准解读

手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读&#xff0c;综合法规核心要求、最新修正及企业合规要点&#xff1a; 一、法规背景与目标 生效与强制时间 发布于2023年8月31日&#xff08;OJ公报&…...

书籍“之“字形打印矩阵(8)0609

题目 给定一个矩阵matrix&#xff0c;按照"之"字形的方式打印这个矩阵&#xff0c;例如&#xff1a; 1 2 3 4 5 6 7 8 9 10 11 12 ”之“字形打印的结果为&#xff1a;1&#xff0c;…...