手撕设计模式——计划生育之单例模式
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 |
---|---|---|
T1 | A-1:分配singleton对象的内存空间 | |
T2 | A-3:将instance指向刚分配的内存地址 | B-1:第一层判断instance是否为null |
T3 | B-2:instance不为null,B线程获得instance引用的对象 | |
T4 | A-2:初始化对象 | |
T5 | A-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):指一个类只有一个实例,且该类能自行创建这个实例的一种模式。
通用实现步骤:
- 私有化构造方法
- 在单例内部创建一个唯一实例
- 提供一个外部获取实例的方法
4.优缺点以及应用场景
优点:
- 提供了唯一实例的全局访问方法,可以优化共享资源的访问
- 避免对象的频繁创建和销毁,可以提高性能
缺点:
- 单例模式的代码基本上在一个类中,违反了单一职责
- 单例模式不易扩展,扩展需要修改原来的代码,违背开闭原则
适用场景:
- 创建一个对象资源消耗过高,并且只需一个
- 只允许使用一个公共访问点
现实应用场景:
- 数据库连接池
- 手机app窗口(大多数app)
- Spring中Bean的默认生命周期
- Windows任务管理器
你的收藏和点赞就是我最大的创作动力,关注我我会持续输出更新!
友情提示:请尊重作者劳动成果,如需转载本博客文章请注明出处!谢谢合作!
【作者:我爱吃菠菜 】
相关文章:
手撕设计模式——计划生育之单例模式
1.业务需求 大家好,我是菠菜啊。80、90后还记得计划生育这个国策吗?估计同龄的小伙伴们,小时候常常被”只生一个好“”少生、优生“等宣传标语洗脑,如今国家已经放开并鼓励生育了。话说回来,现实生活中有计划生育&…...
Mac M3 Pro 部署Flink-1.16.3
目录 1、下载安装包 2、解压及配置 3、启动&测试 4、测试FlinkSQL读取hive数据 以上是mac硬件配置 1、下载安装包 官网:Downloads | Apache Flink 网盘: Flink 安装包 https://pan.baidu.com/s/1IN62_T5JUrnYUycYMwsQqQ?pwdgk4e Flink 已…...
Mysql 的分布式策略
1. 前言 MySQL 作为最最常用的数据库,了解 Mysql 的分布式策略对于掌握 MySQL 的高性能使用方法和更安全的储存方式有非常重要的作用。 它同时也是面试中最最常问的考点,我们这里就简单总结下 Mysq 的常用分布式策略。 2. 复制 复制主要有主主复制和…...
记录一个利用winhex进行图片隐写分离的
前提 是一次大比武里面的题目,属实给我开了眼,跟我之前掌握的关于隐写合并的操作都不一样。 它不是直接在文件里面进行输入文件隐写,叫你输入密码,或者更改颜色,或者偏移位置; 它不是单纯几个文件合并&a…...
压缩映射定理证明
收缩映射定理(又称Banach不动点定理)是一个重要的结果,特别是在分析和应用数学中。 定理(收缩映射定理):假设是一个从度量空间 (X,d) 到自身的函数,如果 是一个收缩映射,即存在常数 …...
Ubuntu20.04.6操作系统安装教程
一、VMware Workstation16安装 选择安装VMware Workstation,登录其官网下载安装包,链接如下: 下载 VMware Workstation Pro 下载后运行安装向导,一直Next即可。 二、Ubuntu镜像下载 ubuntu20.04 选择需要下载的镜像类型下载即…...
(分治算法3)leecode 53 最大子数组和(最大子段和)
题目描述 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 子数组是数组中的一个连续部分。 分治解法 这个问题可以分成从左半边数组找最大子段和从右半部分找最大子段和…...
【C++】模板初级
【C】模板初级 泛型编程函数模板函数模板的概念函数模板格式函数模板的原理函数模板的实例化模板参数的匹配原则 类模板类模板格式类模板的实例化 泛型编程 当我们之前了解过函数重载后可以知道,一个程序可以出现同名函数,但参数类型不同。 //整型 voi…...
eslint 使用单引号,Prettier使用双引号冲突
当 ESLint 规则要求使用单引号 (quotes: single) 而 Prettier 默认使用双引号时,会发生配置冲突。为了解决这个问题,你需要统一这两个工具的配置,确保它们遵循相同的规则。这里推荐两种解决方案: 解决方案 1: 修改 ESLint 配置以…...
进化生物学的数学原理 知识点总结
1、进化论与自然选择 1.1 进化论 1、进化论 过度繁殖 -> 生存竞争 -> 遗传和变异 -> 适者生存 2、用进废退学说与自然选择理论 用进废退:一步适应:变异 适应 自然选择:两步适应:变异 选择 适应 3、木村资生的中性…...
如何挑到高质量的静态IP代理?
在数字化时代,静态住宅IP代理已成为网络活动中不可或缺的一部分。无论是数据采集、网站访问,还是其他需要隐藏真实IP地址的在线活动,高质量的静态住宅IP代理都发挥着至关重要的作用。今天IPIDEA代理IP将详细介绍如何获取高质量的静态住宅IP代…...
vagrant putty错误的解决
使用Vagrant projects for Oracle products and other examples 新创建的虚机,例如vagrant-projects/OracleLinux/8。 用vagrant ssh可以登录: $ vagrant ssh > vagrant: Getting Proxy Configuration from Host...Welcome to Oracle Linux Server …...
图像分割——U-Net论文介绍+代码(PyTorch)
0、概要 原理大致介绍了一下,后续会不断精进改的更加详细,然后就是代码可以对自己的数据集进行一个训练,还会不断完善,相应其他代码可以私信我。 一、论文内容总结 摘要:人们普遍认为,深度网络成功需要数…...
C#进阶-ASP.NET的WebService跨域CORS问题解决方案
在现代的Web应用程序开发中,跨域资源共享(Cross-Origin Resource Sharing, CORS)问题是开发者经常遇到的一个挑战。特别是当前端和后端服务部署在不同的域名或端口时,CORS问题就会显得尤为突出。在这篇博客中,我们将深…...
如何利用TikTok矩阵源码实现自动定时发布和高效多账号管理
在如今社交媒体的盛行下,TikTok已成为全球范围内最受欢迎的短视频平台之一。对于那些希望提高效率的内容创作者而言,手动发布和管理多个TikTok账号可能会是一项繁琐且耗时的任务。幸运的是,通过利用TikTok矩阵源码,我们可以实现自…...
Java高级编程技术详解:从多线程到算法优化的全面指南
复杂度与优化 复杂度与优化在算法中的应用 算法复杂度是衡量算法效率的重要指标。了解和优化算法复杂度对提升程序性能非常关键。本文将介绍时间复杂度和空间复杂度的基本概念,并探讨一些优化技术。 时间复杂度和空间复杂度 时间复杂度表示算法执行所需时间随输…...
Redis 分布式锁过期了,还没处理完怎么办?
为了防止死锁,我们会给分布式锁加一个过期时间,但是万一这个时间到了,我们业务逻辑还没处理完,怎么办? 这是一个分布式应用里很常见到的需求,关于这个问题,有经验的程序员会怎么处理呢ÿ…...
Vue2+Element-ui后台系统常用js方法
el-dialog弹框关闭清空form表单并清空验证 cancelDialog(diaLog, formRef) {this[diaLog] falseif (formRef) {this.$refs[formRef].resetFields()} }页面使用: <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)磁盘顺序读写:保证了消息的堆积2)零拷贝机…...
uniapp地图自定义文字和图标
这是我的结构: <map classmap id"map" :latitude"latitude" :longitude"longitude" markertap"handleMarkerClick" :show-location"true" :markers"covers" /> 记住别忘了在data中定义变量…...
k8s_探针专题
关于探针 生产环境中一定要给pod设置探针,不然pod内的应用发生异常时,K8s将不会重启pod。 需要遵循以下几个原则(本人自己总结,仅供参考): 探针尽量简单,不要消耗过多资源。因为探针较为频繁的…...
MySQL触发器基本结构
1、修改分隔符符号 delimiter $$ 可以修改成$$ //都行 2、创建触发器函数名称 create trigger 函数名 3、什么样的操作出发,操作那个表 after:......之后触发 befor:......之前触发 insert:插入被触发 update:修改被触…...
前缀和(一维前缀和+二维前缀和)
前缀和 定义: 前缀和是指某序列的前n项和,可以把它理解为数学上的数列的前n项和,而差分可以看成前缀和的逆运算。合理的使用前缀和与差分,可以将某些复杂的问题简单化。 用途: 前缀和一般用于统计一个区间的和&…...
web前端五行属性:深入探索与实战解析
web前端五行属性:深入探索与实战解析 在Web前端开发中,五行属性这一概念或许听起来有些陌生。然而,如果我们将其与前端开发的核心理念相结合,就能发现其中蕴含的深刻内涵。本文将从四个方面、五个方面、六个方面和七个方面&#…...
白酒:茅台镇白酒的酒厂社会责任与可持续发展
云仓酒庄豪迈白酒,作为茅台镇的品牌,不仅在产品品质和口感方面有着卓着的表现,在酒厂社会责任和可持续发展方面也做出了积极的探索和实践。 首先,云仓酒庄豪迈白酒注重环境保护和资源利用。酒厂在生产过程中严格控制能源消耗和排放…...
音视频开发_SDL音频播放器的实现
今天向大家介绍一下如何通过 SDL 实现一个PCM音频播放器。这是一个最简单的播放器,它不涉及到音频的解复用,解码等工作。我们只需要将音频原始数据喂给 SDL 音频接口就可以听到悦耳的声音了。在下面的列子中我将向你演示,使用 SDL 做这样一个…...
C语言学习系列:初识C语言
前言,C语言是什么 语言,比如中文、英语、法语、德语等,是人与人交流的工具。 C语言也是语言,不过是一种特殊的语言,是人与计算机交流的工具。 为什么叫C语言呢? 这就要从C语言的历史说起了。 一&#…...
利用反向代理编写HTTP抓包工具——可视化界面
手写HTTP抓包工具——可视化界面 项目描述语言golang可视化fynev2功能代理抓包、重发、记录 目录 1. 示例1.1 主界面1.2 开启反向代理1.3 抓包1.4 历史记录1.5 重发 2. 核心代码2.1 GUI2.1 抓包 3. 结语3.1 传送门 1. 示例 1.1 主界面 1.2 开启反向代理 1.3 抓包 1.4 历史记录…...
下拉框数据被遮挡 且 后续数据无法下拉的 解决方法
目录 前言1. 问题所示2. 原理分析3. 解决方法3.1 添加空白版2.2 调整z-index2.3 父容器的溢出属性2.4 调整样式属性4. 效果图前言 小程序使用的是Uniapp,原理都差不多,索性标题就不标注Uniapp(小程序) 对于该问题调试了一个晚上,最终解决,对此记录下来 1. 问题所示 执…...
课设--学生成绩管理系统(二)
欢迎来到 Papicatch的博客 目录 🐋引言 🦈编写目的 🦈项目说明 🐋产品介绍 🦈产品概要说明 🦈产品用户定位 🦈产品中的角色 🐋 产品总体业务流程图 🐋 产品功…...
腾讯邮箱网页版/seo技术服务外包
51%的企业在过去12月内发生过数据泄漏面对日益复杂的安全环境,多样化的攻击手段,传统的防护已经失效,你的安全团队是否做好了准备? 深井式的管理架构,各自封闭的信息系统,无迹可寻的内部泄漏,防…...
求大神帮忙做网站/搜索引擎营销有哪些
JavaScript For 循环循环在编程中用于自动执行重复性任务。例如,假设我们要打印“ Hello World” 10次。可以如下所示进行:document.write("Hello World");document.write("Hello World");document.write("Hello World");…...
网站欢迎页代码/武汉网络推广外包公司
Android通过gps获取定位的位置数据和gps经纬度的代码如下package com.action.android_test;import android.location.Location;import android.location.LocationListener;import android.location.LocationManager;import android.os.Bundle;import android.app.Activity;impo…...
佛山高端网站建设/公司推广策划方案
在vue使用echarts时,可能会遇到这样的问题,就是直接刷新浏览器,或者数据变化时,echarts不更新? 这是因为Echarts是数据驱动的,这意味着只要我们重新设置数据,那么图表就会随之重新渲染,这是实现…...
免费网站建站一级av/网络培训平台
http://ticktick.blog.51cto.com/823160/d-15 原创 Android音频开发(7):使用 OpenSL ES AP.. 2016-05-08 19:19:47本文主要介绍了 Android OpenSL ES 的框架设计和常见 API 的用法,并给出了示例代码,让初学者能够更加…...
桑拿网站只做/高级搜索百度
一般我们下载SCI论文先在kopernio中查找,这个插件确实很方便。万一查找不到的话再去sci-hub上进行下载。 一、Kopernio插件 Kopernio是基于chrome浏览器的一款免费插件,通过人工智能技术使得用户可一键访问其所在机构订购的期刊论文,大大节省…...