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

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任务管理器

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CCAA质量管理【学习笔记】​​ 备考知识点笔记(五)质量设计方法与工具

第五节 质量设计方法与工具 1 任 务 分 解 法 1.1 概念 任务分解法&#xff0c;又称工作分解结构 (Work Breakdown Structure, 简 称 WBS) 。WBS 指以可交付成果为 导向&#xff0c;对项目团队为实现项目目标并完成规定的可交付成果而执行的工作所进行的层次分解。W…

Swift开发——循环执行方式

本文将介绍 Swift 语言的循环执行方式 01、循环执行方式 在Swift语言中,主要有两种循环执行控制方式: for-in结构和while结构。while结构又细分为当型while结构和直到型while结构,后者称为repeat-while结构。下面首先介绍for-in结构。 循环控制方式for-in结构可用于区间中的…

电子科技大学卓中卓二轮——分析笔记

1. 子系统的关键工作原理 在Linux子系统&#xff08;Subsystem for Linux, 简称WSL&#xff09;中&#xff0c;API&#xff08;应用程序编程接口&#xff09;的转换和映射是一个关键过程&#xff0c;目的是让Windows应用程序能够与Linux环境中的系统调用无缝交互。WSL使用了名…

JUnit 5学习笔记

JUnit 5 学习笔记 1.JUnit5的改变2.JUnit5常用注解及测试2.1 DisplayName/Disabled/BeforeEach/AfterEach/BeforeAll/AfterAll2.2 Timeout2.3 RepeatedTest 3.断言3.1 简单断言3.2 数组断言3.3 组合断言3.4 异常断言3.5 超时断言3.6 快速失败 4.前置条件5.嵌套测试6.参数化测试…

C++ string字符串的使用和简单模拟实现

目录 前言 1. string简介 2. string的使用和简单模拟实现 2.1 string类的定义 2.2 string(),~string()和c_str() 2.2 size&#xff0c;重载符号[ ]&#xff0c;begin和end函数 2.3 push_back&#xff0c;reserve&#xff0c;append&#xff0c;运算符重载 2.4 insert和…

12306 火车票价格解析 (PHP 解析)

1. 从接口拿数据 日期 出发站 终点站 都填上 xxx/otn/leftTicketPrice/queryAllPublicPrice?leftTicketDTO.train_date2024-06-15&leftTicketDTO.from_stationBJP&leftTicketDTO.to_stationSJP&purpose_codesADULT 返回的数据是这样的 {"validateMess…

2786. 访问数组中的位置使分数最大

这并不是一个难题,但是我确实在做题中得到了一些启发,所以记录一下 先讲一讲这个题目的做法: 首先不难想到这是一个dp问题,(由 i 可以跳到 j ) 而且应该不难, 要不然就不是medium了,doge 那么,暴力的dp就是: dp[j] max (dp[i] nums OR dp[j] dp[i] nums - x) , i<j, 前…

JVM 垃圾回收器

一、垃圾回收器类型 如果说垃圾收集算法是内存回收的方法论&#xff0c;那么垃圾收集器就是内存回收的具体 实现。下图展示了7种作用于不同分代的收集器&#xff0c;其中用于回收新生代的收集器 包括Serial、PraNew、Parallel Scavenge&#xff0c;回收老年代的收集器包括Seri…

采煤vr事故灾害应急模拟救援训练降低生命财产损失

在化工工地&#xff0c;设备繁多、环境复杂&#xff0c;潜藏着众多安全隐患&#xff0c;稍有不慎便可能引发安全事故。为了保障工地的安全&#xff0c;我们急需一套全面、高效的安全管理解决方案。web3d开发公司深圳华锐视点研发的工地安全3D模拟仿真隐患排查系统&#xff0c;正…

sql优化之利用聚簇索引减少回表次数:limit 100000,10

1. 问题描述 产品&#xff1a;我要对订单列表页做一个分页功能&#xff0c;每页10条数据&#xff0c;商家可以根据金额过滤订单 技术&#xff1a;好的&#xff0c;我写一个sql实现分页&#xff0c;x表示偏移页数&#xff0c;自测limit 10,10耗时200ms&#xff1a; SELECT * …

【2024最新精简版】MyBatis面试篇

文章目录 mybatis内部实现过程mybatis延迟加载请说说MyBatis的工作原理mybatis接口里的方法,参数不同时能重载吗mybatis分页插件的原理是什么&#xff1f;mybatis的一级、二级缓存&#x1f44d;mybatis如何实现多表查询mybatis如何实现批量插入&#x1f44d;mybatis动态SQL标签…

Spring Boot 自定义Starter

自定义starter 创建pom项目 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.ap…

可视化剪辑,账号矩阵管理,视频分发,聚合私信多功能一体化营销工具 源代码开发部署方案

可视化剪辑&#xff0c;账号矩阵管理&#xff0c;视频分发&#xff0c;聚合私信多功能一体化营销工具 源代码开发部署方案 可视化剪辑&#xff1a; 可视化剪辑开发是一种通过图形化界面和拖放操作&#xff0c;以可视化的方式进行影片剪辑和编辑的开发方法。它可以让非专业用户…

【NLP练习】Transformer中的位置编码

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、什么是位置编码 1. 位置编码定义 Transformer 模型中的位置编码是为了在处理序列数据时引入位置信息&#xff0c;以便模型能够分辨输入序列中不同位置的词…

180.二叉树:二叉搜索树(力扣)

代码解决 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* Tre…

实时交通 | 城市交通态势采集及可视化操作(定时运行)

一、前言 交通态势数据是关于交通状况的一种量化描述&#xff0c;它提供了关于道路网络运行状态的详细信息。交通态势数据指的是根据车流入量和车流出量的定义&#xff0c;衡量整个全局交通区域交通态势的数据。这些数据通常从车辆GPS轨迹数据中提取&#xff0c;包括车辆行驶速…

Oracle备份失败处理,看这一篇就够了!

作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;10余年DBA工作经验&#xff0c; Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主&#xff0c;全网粉丝10万 擅长主流Oracle、MySQL、PG、高斯及Greenplum备份恢复&#xff0c; 安装迁移&#xff0c;性能优化、故障…

时代巨兽!深度神经网络如何改变我们的世界?

深度神经网络 1、 简介1.1 定义深度神经网络1.2 深度学习的发展历程1.3 深度神经网络的应用领域 2、深度神经网络的基本原理2.1 神经元层2.1.1 神经元2.1.2 神经元层 2.2 前向传播2.3 反向传播2.4 激活函数2.4.1、作用2.4.2、常见激活函数2.4.3、选择激活函数的考虑 2.5 损失函…

Selenium+Pytest自动化测试框架能碰撞出什么样的火花

前言 selenium自动化 pytest测试框架 本章你需要 一定的python基础——至少明白类与对象&#xff0c;封装继承 一定的selenium基础——本篇不讲selenium&#xff0c;不会的可以自己去看selenium中文翻译网 一、测试框架简介 测试框架有什么优点呢&#xff1a; 代码复用率高…

网信办部署开展清朗专项行动,严打色情等违法信息外链

据网信中国官方公众号&#xff0c;近日&#xff0c;中央网信办专门印发通知&#xff0c;在全国范围内部署开展为期2个月的“清朗打击违法信息外链”专项行动。 据介绍&#xff0c;本次专项行动聚焦违法信息外链问题易发多发的8个重点环节开展整治。 一是账号环节。在账号头像…