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

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.mfbz.cn/a/704419.html

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

相关文章

SqlSugar使用DbFirst对象根据数据库表结构创建实体类-C#

本文所述开发环境&#xff1a;.C#、NET8、Visual Studio2022 1. 在项目中安装SqlSugar 在Visual Studio2022中新建一个 C# 的控制台应用程序&#xff0c;框架选择 .Net8。新建后如下图所示&#xff1a; 然后打开NuGet程序包管理器 搜索 SqlSugarCore 并安装 安装后在解决方案…

资源分享—2021版市级制图规范符号库

汇总整理超图平台软件相关的各类资源&#xff08;包括但不限于符号库、地图模板、地理处理模型等&#xff09;&#xff0c;助力项目的高效制图、提高数据生产效率等业务。 本次分享新版国土空间规划【2021版市级制图规范符号库】&#xff0c;提供SuperMap格式符号库下载。 1.市…

数据结构的队列,链表,栈的基础操作

1&#xff1a;队列 #include <stdio.h>#include <stdlib.h>#include "./02队列.h"/** function: 创建一个空的队列* param [ in] * param [out] * return */Sequeue* xinduilie(){Sequeue* sq (Sequeue*)malloc(sizeof(Sequeue)); if(N…

Java 反射机制 -- Java 语言反射的概述、核心类与高级应用

大家好,我是栗筝i,这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 010 篇文章,在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验,并希望进一步完善自己对整个 Java 技术体系来充实自…

镜像拉取失败:[ERROR] Failed to pull docker image

问题描述 执行 bash docker/scripts/dev_start.sh 命令提示错误&#xff1a; permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post “http://%2Fvar%2Frun%2Fdocker.sock/v1.45/images/create?fromImageregistry.b…

在Lua解释器中注册自定义函数

本文目录 1、引言2、函数注册2.1注册原理 2.2 注册函数 3、实操3.1 编写注册函数3.2编写测试代码 4、结论 文章对应视频教程&#xff1a; 暂无&#xff0c;可以关注我的B站账号等待更新。 点击图片或链接访问我的B站主页~~~ 1、引言 在之前的博客中&#xff0c;已经介绍了如何…

JAVA小知识17:数组,从0基础到掌握

数组&#xff0c;无论在哪种编程语言当中都是最基础&#xff0c;最广泛使用的一种线性表数据结构&#xff0c;这篇文章将从多个角度来从浅入深的讲述数组。 本文讲述了数组的概念&#xff0c;定义&#xff0c;初始化方法以及如何遍历数组&#xff0c;如何赋值&#xff0c;关于数…

4. Revit API UI 之 Ribbon(界面)

4. Revit API UI 之 Ribbon&#xff08;界面&#xff09; 第二篇中&#xff0c;我们提到了IExternalApplication&#xff0c;该接口需要实现两个方法&#xff1a;Revit启动时调用的OnStartup 方法&#xff0c;和Revit关闭时调研的OnShutdown 方法。文中还给了个例子&#xff0…

剧透!「飞凌嵌入式技术创新日」3大亮点抢先看

6月25日&#xff0c;飞凌嵌入式技术创新日&#xff08;北京站&#xff09;即将开幕&#xff0c;一场嵌入式前沿科技的高端局就在眼前。 飞凌嵌入式作为国内较早专业从事嵌入式技术的企业&#xff0c;凭借18年的行业深耕和丰富的技术积累&#xff0c;已在业界赢得了广泛的影响力…

LabVIEW电源适应能力检测系统

随着工业自动化程度的提高&#xff0c;电源质量直接影响设备的稳定运行。利用LabVIEW开发一个单相电源适应能力检测系统&#xff0c;该系统通过智能化和自动化测试&#xff0c;提高了测试效率&#xff0c;减少了人为错误&#xff0c;保证了电源质量的可靠性。 项目背景 在现代…

大厂面试必备:如何轻松实现分布式Session管理?

我是小米,一个喜欢分享技术的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!​​​​​​​ 大家好,我是小米,一个29岁的技术爱好者,喜欢分享各种技术干货。今天我们来聊一聊阿里巴巴面试中的一个经典问题:如何实现分布式Sess…

HCIA12 NAT网络地址转换实验

NAT&#xff08;Network Address Translation&#xff09;是将 IP 报头中的 IP 地址转换为另一个 IP 地址的过程。主要俩好处&#xff1a; • 有效避免来自外网的攻击&#xff0c;可以很大程度上提高网络安全性。 • 控制内网主机访问外网&#xff0c;同时也可以控制外网…

你的医书是假的!批评《DDD诊所——聚合过大综合症》(合集)

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 &#xff08;《你的医书是假的&#xff01;》原文写于2023年8月&#xff0c;以系列文章方式发表&#xff0c;现合并成一篇文章&#xff09; 一、说在前面 这两天在“ Thoughtworks洞见…

Mac vscode could not import github.com/gin-gonic/gin

问题背景&#xff1a; 第一次导入一个go的项目就报红 问题分析&#xff1a; 其实就是之前没有下载和导入gin这个web框架包 gin是一个golang的微框架&#xff0c;封装比较优雅&#xff0c;API友好&#xff0c;源码注释比较明确。 问题解决&#xff1a; 依次输入以下命令。通…

【STM32】输入捕获应用-测量脉宽或者频率(方法1)

图1 脉宽/频率测量示意图 1 测量频率 当捕获通道TIx 上出现上升沿时&#xff0c;发生第一次捕获&#xff0c;计数器CNT 的值会被锁存到捕获寄存器CCR中&#xff0c;而且还会进入捕获中断&#xff0c;在中断服务程序中记录一次捕获&#xff08;可以用一个标志变量来记录&#…

阿里最强开源大模型本地部署、API调用和WebUI对话机器人

阿里巴巴通义千问团队发布了Qwen2系列开源模型&#xff0c;该系列模型包括5个尺寸的预训练和指令微调模型&#xff1a;Qwen2-0.5B、Qwen2-1.5B、Qwen2-7B、Qwen2-57B-A14B以及Qwen2-72B。对比当前最优的开源模型&#xff0c;Qwen2-72B在包括自然语言理解、知识、代码、数学及多…

新加坡裸机云多IP服务器为何适合跨境外贸业务

新加坡裸机云多IP服务器在跨境外贸业务中展现出了卓越的适配性&#xff0c;其独特优势为外贸企业提供了强大的支持。以下将详细阐述为何新加坡裸机云多IP服务器是跨境外贸业务的理想选择。 首先&#xff0c;新加坡裸机云多IP服务器在性能上表现出色。由于去除了虚拟化层的开销&…

聚观早报 | 粉笔将推AI智能老师;比亚迪宋L DM-i车型官宣

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 6月13日消息 粉笔将推AI智能老师 比亚迪宋L DM-i车型官宣 真我GT7 Pro配置全面升级 理想发布最新单周销量榜单 …

如何免费用 Qwen2 辅助你翻译与数据分析?

对于学生用户来说&#xff0c;这可是个好消息。 开源 从前人们有一种刻板印象——大语言模型里好用的&#xff0c;基本上都是闭源模型。而前些日子&#xff0c;Meta推出了Llama3后&#xff0c;你可能已经从中感受到现在开源模型日益增长的威力。当时我也写了几篇文章来介绍这个…

SMT智能车间MES系统的实施方案

SMT行业中MES系统实施的关键特征&#xff1a; SMT包括上下板设备&#xff0c;印刷设备&#xff0c;贴片设备&#xff0c;回流焊炉&#xff0c;AOI。AOI的全称是自动光学检查&#xff0c;它基于光学原理来检测焊接生产中遇到的常见缺陷。测试设备&#xff0c;测试设备等MES解决…