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

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.mzph.cn/pingmian/26879.shtml

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

相关文章

2024网络安全学习路线 非常详细 推荐学习

关键词&#xff1a;网络安全入门、渗透测试学习、零基础学安全、网络安全学习路线 首先咱们聊聊&#xff0c;学习网络安全方向通常会有哪些问题 1、打基础时间太长 学基础花费很长时间&#xff0c;光语言都有几门&#xff0c;有些人会倒在学习 linux 系统及命令的路上&#…

嵌套查询(二)-谓词EXISTS实现嵌套查询

一、EXISTS谓词 1、作用&#xff1a;用于判断一个子查询的结果是否为空 2、使用语法&#xff1a; 【NOT】EXISTS&#xff08;子查询&#xff09; 语义&#xff1a;如果子查询的查询结果不为空&#xff0c;则EXISTS为真&#xff0c;否则为假 二、举例 1、举例1&#xff1a…

【数据结构 |集合框架、泛型】初始集合框架、时间(空间)复杂度、简单认识泛型

✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天开心哦&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; &#x1f388;丠丠64-CSDN博客&#x1f388; ✨✨ 帅哥美女们&#xff0c;我们共同加油&#xff01;一起…

【启明智显彩屏应用】Model3A 7寸触摸彩屏的充电桩应用方案

一、充电桩概述 &#xff08;一&#xff09;充电桩诞生背景 随着社会的进步和人们生活质量的提升&#xff0c;汽车已逐渐融入每个家庭的日常生活中。然而&#xff0c;汽车数量的激增也带来了严重的环境污染问题&#xff0c;特别是尾气排放。为了应对这一挑战&#xff0c;新能源…

用PlayCanvas打造一个3D模型

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 基于 PlayCanvas 的 3D 物理场景开发 应用场景介绍 PlayCanvas 是一款功能强大的 3D 引擎&#xff0c;可用于创建各种类型的 3D 体验&#xff0c;包括游戏、模拟和交互式可视化。本技术博客将介绍如何使用 Pl…

怎么把wma格式转化为mp3格式?四种wma格式转成MP3格式的方法

怎么把wma格式转化为mp3格式&#xff1f;转换WMA格式音频文件为MP3格式是一个常见的需求&#xff0c;尤其是在我们希望在不同设备或平台上播放音频时。WMA格式虽然在Windows系统中较为常见&#xff0c;但在其他平台上的兼容性可能不如MP3格式。因此&#xff0c;将WMA转换为MP3是…

基于Spring Boot的智能分析平台

项目介绍&#xff1a; 智能分析平台实现了用户导入需要分析的原始数据集后&#xff0c;利用AI自动生成可视化图表和分析结论&#xff0c;改善了传统BI系统需要用户具备相关数据分析技能的问题。该项目使用到的技术是SSMSpring Boot、redis、rabbitMq、mysql等。在项目中&#…

在 Wed 中应用 MyBatis(同时使用MVC架构模式,以及ThreadLocal 事务控制)

1. 在 Wed 中应用 MyBatis&#xff08;同时使用MVC架构模式&#xff0c;以及ThreadLocal 事务控制&#xff09; 文章目录 1. 在 Wed 中应用 MyBatis&#xff08;同时使用MVC架构模式&#xff0c;以及ThreadLocal 事务控制&#xff09;2. 实现步骤&#xff1a;1. 第一步&#xf…

Vulnhub-DC-1,7

靶机IP:192.168.20.141 kaliIP:192.168.20.128 网络有问题的可以看下搭建Vulnhub靶机网络问题(获取不到IP) 前言 1和7都是Drupal的网站&#xff0c;只写了7&#xff0c;包含1的知识点 信息收集 用nmap扫描端口及版本号 进入主页查看作者给的提示&#xff0c;不是暴力破解的…

nodejs湖北省智慧乡村旅游平台-计算机毕业设计源码00232

摘 要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;旅游行业当然也不能排除在外。智慧乡村旅游平台是以实际运用为开发背景&#xff0c;运用软件工程开发方法&#xff0c;采…

Weighted A* 改进型(1):XDP

本文的主要内容来自于文献[1]&#xff0c;总的来说这篇文献给我的感觉就是理论证明非常精妙&#xff0c;最后的实际效果也是提升的非常明显。 在Introduction中作者给出了一般Best first search&#xff08;BFS&#xff0c;常用的包括A *&#xff0c;weighted A * &#xff0c…

TK防关联引流系统:全球多账号运营,一“键”掌控!

在TikTok的生态系统中&#xff0c;高效管理多个账号对于品牌推广的成功起着决定性的作用。TK防关联引流系统&#xff0c;作为一款专门为TikTok用户打造的强大工具&#xff0c;为全球范围内的多账号运营提供了坚实的支持。 TK防关联引流系统的核心优势体现在以下几个方面&#x…

anaconda安装pytorch-快速上手99%可以(可以虚拟环境OR不进行虚拟环境)

一、预备工作 先检查自己是否有anaconda 在cmd里面输入conda --version查看 二、在anaconda中创建虚拟环境 1.1 打开Anaconda Prompt 1.2 进行自定义安装python 将其中的自定义地址和版本换成自己想安装的地址和版本 我这里安装的地址是E:\Anaconda\DL,python版本是3.8.3…

uniapp地图自定义文字和图标

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

Sqoop学习详细介绍!!

一、Sqoop介绍 Sqoop是一款开源的工具&#xff0c;主要用于在Hadoop(HDFS/Hive/HBase)与传统的数据库(mysql、postgresql...)间进行数据的传递&#xff0c;可以将一个关系型数据库&#xff08;例如 &#xff1a; MySQL ,Oracle ,Postgres等&#xff09;中的数据导进到Hadoop的H…

虚拟声卡实现音频回环

虚拟声卡实现音频回环 一、电脑扬声器播放声音路由到麦克风1. Voicemeeters安装设置2. 音频设备选择 二、回声模拟 一、电脑扬声器播放声音路由到麦克风 1. Voicemeeters安装设置 2. 音频设备选择 以腾讯会议为例 二、回声模拟 选中物理输入设备“Stereo Input 1”和物理输出设…

GlusterFS企业分布式存储

GlusterFS 分布式文件系统代表-nfs常见分布式存储Gluster存储基础梳理GlusterFS 适合大文件还是小文件存储&#xff1f; 应用场景术语Trusted Storage PoolBrickVolumes Glusterfs整体工作流程-数据访问流程GlusterFS客户端访问流程 GlusterFS常用命令部署 GlusterFS 群集准备环…

修改版的VectorDBBench更好用

原版本VectorDBBench的几个问题 在这里就不介绍VectorDBBench是干什么的了&#xff0c;上官网即可。 1.并发数设置的太少 2.测试时长30秒太长 3.连接milvus无用户和密码框&#xff0c;这个是最大的问题 4.修改了一下其它参数 由于很多网友发私信问一些milvus的相关技术问…

【机器学习】支持向量机(个人笔记)

文章目录 SVM 分类器的误差函数分类误差函数距离误差函数C 参数 非线性边界的 SVM 分类器&#xff08;内核方法&#xff09;多项式内核径向基函数&#xff08;RBF&#xff09;内核 源代码文件请点击此处&#xff01; SVM 分类器的误差函数 SVM 使用两条平行线&#xff0c;使用…

基于flask的网站如何使用https加密通信

文章目录 内容简介网站目录示例生成SSL证书单独使用Flask使用WSGI服务器Nginx反向代理参考资料 内容简介 HTTPS 是一种至关重要的网络安全协议&#xff0c;它通过在 HTTP 协议之上添加 SSL/TLS 层来确保数据传输的安全性和完整性。这有助于防止数据在客户端和服务器之间传输时…