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

news2025/3/5 0:58:19

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.coloradmin.cn/o/1815216.html

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

相关文章

【Kadane】Leetcode 918. 环形子数组的最大和【中等】

环形子数组的最大和 给定一个长度为 n 的环形整数数组 nums &#xff0c;返回 nums 的非空 子数组 的最大可能和 。 环形数组 意味着数组的末端将会与开头相连呈环状。形式上&#xff0c; nums[i] 的下一个元素是 nums[(i 1) % n] &#xff0c;nums[i] 的前一个元素是 nums…

SortTable.js + vxe-table 实现多条批量排序

环境: vue3+vxe-table+sorttable.js 功能: 实现表格拖动排序,支持单条排序,多条排序 实现思路: sorttable.js官网只有单条排序的例子,网上也都是简单的使用,想要实现多条排序,就要结合着表格的复选框功能,在对其勾选的行统一计算! 最终效果: 实现代码 <template>…

PointNet论文导读

PointNet论文导读 关键点&#xff1a;网络结构输入数据特点&#xff1a;网络关键模块&#xff1a; 关键点&#xff1a; 1.设计一个新颖的网络来处理无序的点云数据&#xff1b; 2.pointnet网络可以被训练用来处理分类、部件分割和场景分割多种任务&#xff1b; 3.提供了数据…

WordPress模板推荐

WordPress外贸主题 wordpress跨境电商独立站主题&#xff0c;wordpress外贸建站模板。 手机配件wordpress外贸网站模板 充电器、移动电源、手机膜、手机电池、手机壳、手机转接头等手机配件wordpress外贸网站模板。 毛巾WordPress外贸主题 毛巾、面巾、婴童毛巾、浴巾、方巾、…

【云原生】Kubernetes----Helm包管理器

目录 引言 一、Helm概述 1.Helm价值概述 2.Helm的基本概念 3.Helm名词介绍 二、安装Helm 1.下载二进制包 2.部署Helm环境 3.添加补全信息 三、使用Helm部署服务 1.创建chart 2.查看文件信息 3.安装chart 4.卸载chart 5.自定义chart服务部署 6.版本升级 7.版本…

微软Win11 22H2/23H2六月更新补丁KB5039212发布!附完整更新日志

系统之家于6月12日发出最新报道&#xff0c;微软向Win11用户推出六月更新补丁KB5039212&#xff0c;22H2版本号升为22621.3737&#xff0c;23H2版本号升为22631.3737。此次更新解决了可能导致系统无法从休眠状态恢复等问题。感兴趣的用户可以继续阅读以下内容&#xff0c;获取更…

项目太大导致报错:JavaScript堆内存已满

1.问题 启动一个Vue项目的时候遇到了如下的报错 Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory 无效的标记压缩导致接近了堆上限&#xff0c;分配失败 - JavaScript内存不足 2.解决方法 我查阅了网上的资料&#xff0c;似乎…

服装服饰展示预约小程序的效果是什么

服装作为生活必需品&#xff0c;其品牌大众小众类别非常多&#xff0c;大街小巷经销店/小摊贩等&#xff0c;线上线下竞争激烈且客户选择性广&#xff0c;尤其是厂商或连锁品牌&#xff0c;在宣传获客转化等方面都需要不断找寻渠道。 通常线上流量比之线下更容易获取&#xff…

Ubuntu server 24 (Linux) 安装客户端(windows/linux) Zabbix 7.0 LTS Zabbix agent2

一 Ubuntu(linux)安装客户端 1 Ubuntu 24 安装Zabbix agent2 #安装agent库 sudo wget https://repo.zabbix.com/zabbix/7.0/ubuntu/pool/main/z/zabbix-release/zabbix-release_7.0-1ubuntu24.04_all.deb sudo dpkg -i zabbix-release_7.0-1ubuntu24.04_all.deb sudo apt u…

【git使用三】git工作机制与命令用法

目录 git工作机制和相关概念 四个重要区域 分支的概念 上传代码到远程分支的基本流程 克隆代码 仓库同步 开发者如何提交代码到远程仓库分支 1.初始化本地仓库 2.关联本地仓库和远程仓库 创建关联 查看关联情况 如何解除关联 3.推送代码到远程仓库 3.1先下拉远程…

wms海外仓系统多少钱?什么价格才合理,一篇文章介绍清楚

在海外仓竞争异常激烈的今天&#xff0c;提升仓管效率&#xff0c;降低运营成本变得迫在眉睫。尤其是对中小型海外仓和一些刚处于起步阶段的海外仓来说&#xff0c;能选到一个高性价比&#xff0c;功能完善&#xff0c;合作灵活风险低的wms海外仓系统&#xff0c;并不是一件简单…

sqlserver使用链接服务器将本地服务器数据库表数据更新到远程数据库中

需求&#xff1a; 需要将本地sqlserver服务器某个数据库当中的某个表的数据&#xff0c;更新到远程服务器的同名数据库中的同名的表中。 sql代码如下&#xff1a; insert into TableA(Col1,Col2...) select * from [10.14.7.65\CS2014].[NEVTestDB2.0].[dbo].[TableA]将本地…

【CS.PL】Lua 编程之道: 控制结构 - 进度24%

3 初级阶段 —— 控制结构 文章目录 3 初级阶段 —— 控制结构3.1 条件语句&#xff1a;if、else、elseif3.2 循环语句&#xff1a;for、while、repeat-until3.2.1 输出所有的命令行参数3.2.2 while.lua3.2.3 repeat.lua及其作用域 &#x1f525;3.2.4 for.lua (For Statement)…

基于MMrotate旋转目标框检测(安装说明与踩坑记录)

一、简述 MMRotate 是一款基于 PyTorch 的旋转框检测的开源工具箱&#xff0c;是 OpenMMLab 项目的成员之一。里面包含了rcnn、faster rcnn、r3det等各种旋转目标的检测模型&#xff0c;适合于遥感图像领域的目标检测。 基础知识&#xff1a;什么是旋转目标检测 二、MMRotate…

POSEMANIACS图片库下载

POSEMANIACS - Royalty free 3d pose reference for all artists是一个很不错得姿态库图片网站​&#xff0c;可以用作ControlNet的原始图片。 就是加载速度有点慢。一个个手工点击​查看或下载更是费时费力。 点击图片&#xff0c;可以分析地址为 https://www.posemaniacs.c…

雪球产品可能要远离普通人了

最近有消息说&#xff0c;在年初发生大规模敲入事件后&#xff0c;雪球产品的购买门槛可能从300w提升至1000w。 那么在这个时间&#xff0c;了解一下雪球产品到底是什么&#xff0c;运行原理是什么。 第一种 经典雪球 经典雪球比较容易理解&#xff0c;设定好了固定的敲出条件…

安装Windows版Nessus

安装Windows版Nessus 1.前言2.Nessus安装3.安装具体步骤4.文件版许可证 1.前言 之前有安装过Ubuntu版本的Nessus的&#xff0c;这次使用Windows来安装一下。 安装包获取&#xff1a;公众号回复Nessus_Windows。公众号在文章最下面推广中可以看到。 2.Nessus安装 这里直接使…

使用 C# 学习面向对象编程:第 8 部分

抽象方法 亲爱的读者&#xff0c;本文是 OOP 的第四大支柱&#xff0c;也是最后一大支柱。对于 OOP 初学者来说&#xff0c;这很容易让人困惑。因此&#xff0c;我们用非常简单的语言提供了一个示例。 “抽象用于管理复杂性。无法创建抽象类的对象。抽象类用于继承。” 例如…

11_2、多态性:虚函数

虚函数与抽象类 虚函数概念声明虚析构函数 抽象类纯虚函数抽象类 虚函数 概念 在赋值兼容规则中&#xff0c;基类类型的指针指向派生类对象时&#xff0c;通过此指针只能访问从基类继承来的同名成员。 如果我们希望通过指向派生类对象的基类指针&#xff0c;访问派生类中的同…

淘宝评论数据信息接口

淘宝评论API接口是一种用于获取淘宝用户评论信息的接口&#xff0c;联讯数据可以帮助商家和消费者获取到商品的评价信息&#xff0c;以便更好地了解商品的质量和用户体验。以下是关于淘宝评论API接口的一些内容&#xff1a; 一、接口功能 淘宝评论API接口提供了商品评价信息的…