设计模式--单例模式【创建型模式】
设计模式的分类
我们都知道有 23 种设计模式,这 23 种设计模式可分为如下三类:
- 创建型模式(5 种):单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。
- 结构型模式(7 种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
- 行为型模式(11 种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

设计模式系列文章传送门
设计模式的 7 大原则
什么是单例模式
单例模式是指在内存中只会创建且仅创建一次对象的设计模式,在程序中多次使用同一个对象时,为了防止频繁地创建对象使得 JVM 内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象,这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例模式的分类
单例模式有两种类型,如下:
- 饿汉式:类加载时候就会创建该类的单例对象。
- 懒汉式:类加载时候并不会创建单例对象,只有首次调用该对象时才会创建。
静态变量实现单例模式
静态变量实现单例模式是一种饿汉式的实现,代码如下:
public class StaticSingleton {//私有构造方法private StaticSingleton() {}//创建静态单例对象(是一个成员变量)private static final StaticSingleton STATIC_SINGLETON= new StaticSingleton();//对外提供静态方法获取静态单例对象public static StaticSingleton getInstance() {return STATIC_SINGLETON;}}
静态变量单例模式的关键是类的构造方法私有,同时创建一个静态私有的实例对象,通过静态方法来让调用者来获取该单例对象,同时该类加载的时候就会创建该单例对象。
静态代码块实现单例模式
静态代码块实现单例模式和静态变量实现单例模式基本相同,也是一种饿汉式的实现,代码如下:
public class StaticBlockSingleton {//私有构造方法private StaticBlockSingleton() {}//创建静态单例对象(是一个成员变量)private static final StaticBlockSingleton STATIC_SINGLETON;//静态代码块创建实例对象static {STATIC_SINGLETON = new StaticBlockSingleton();}//对外提供静态方法获取静态单例对象public static StaticBlockSingleton getInstance() {return STATIC_SINGLETON;}}
懒汉单例模式–线程不安全
线程不安全的懒汉单例模式实现代码如下:
public class NotSafeSingleton {//私有构造方法private NotSafeSingleton() {}//定义一个静态私有的成员变量private static NotSafeSingleton staticSingleton;//对外提供静态方法获取静态单例对象 没有提前创建对象 因此是懒汉模式public static NotSafeSingleton getInstance() {if (staticSingleton == null) {staticSingleton = new NotSafeSingleton();}return staticSingleton;}}
为什么说上面这种写法不安全呢?
乍一看确实没有什么问题,我们提供了一个静态方法供调用者获取单例对象,在静态方法中我们先进行了对象为 null 判断,似乎没有什么问题,在没有并发的情况下确实没有问题,但是如果有并发呢?两个调用者线程同时进入了 staticSingleton == null 判断,此时谁也没有执行对象的创建,因此都可以继续往下执行 new NotSafeSingleton() 这句代码,至此这种单例模式为什么不是线程安全的已经很明了了,我们在项目开发中也要考虑这种并发的场景。
懒汉单例模式–线程安全
上面分享了线程不安全的懒汉模式实现,现在我们分享一下线程安全的懒汉模式,这里使用 synchronized 来保证线程安全,代码如下:
public class SafeSingleton {//私有构造方法private SafeSingleton() {}//定义一个静态私有的成员变量private static SafeSingleton staticSingleton;//对外提供静态方法获取静态单例对象 没有提前创建对象 因此是懒汉模式 使用 synchronized 进行加锁public static synchronized SafeSingleton getInstance() {if (staticSingleton == null) {staticSingleton = new SafeSingleton();}return staticSingleton;}}
使用了 synchronized 之后就不会出现两个线程同时执行 staticSingleton == null 的判断了,因此也就没有了线程安全问题。
懒汉单例模式–双重检查(Double Check)
上面分享了使用 synchronized 来实现线程安全的懒汉单例模式,现在我们分享一种更优雅的线程安全的懒汉模式,代码如下:
public class DoubleCheckSingleton {//私有构造方法private DoubleCheckSingleton() {}//定义一个静态私有的成员变量private static DoubleCheckSingleton staticSingleton;//对外提供静态方法获取静态单例对象 没有提前创建对象 因此是懒汉模式 使用 synchronized 进行加锁public static DoubleCheckSingleton getInstance() {//第一次为空判断if (staticSingleton == null) {//加锁synchronized (DoubleCheckSingleton.class){//第二次为空判断if(staticSingleton == null){staticSingleton = new DoubleCheckSingleton();}}}return staticSingleton;}}
对比上面直接在方法上使用了 synchronized,这里调整了 synchronized 的位置,让锁的范围变的更小,同时也提升了效率,因此懒汉单例模式真正发生线程不安全的地方就是创建对象的那行代码,而创建的操作只需要进行一次就可以了,单量的请求都不会走到创建对象的这行代码,因此我们没必要把 synchronized 加在方法上,因此就有了双重检查的懒汉单例模式,为什么需要双重检查?这种双重检查懒汉单例模式真的就是完美的吗?
为什么需要双重检查?
- 假设有线程 1 和现成 2 都来调用 getInstance 方法获取单例对象,进入第一个 staticSingleton == null 的判断时候,线程 1 和线程 2 都满足条件,都可以继续往下执行。
- 接着线程 1 获取到锁,再次进行 staticSingleton == null 的判断,此时 staticSingleton 任然为 null,线程 1 就会开始创建对象,线程 2 等待获取锁。
- 线程 1 创建完对象后释放锁,线程 2 获取到锁后来执行 staticSingleton == null 判断,因为线程 1 已经创建了对象,staticSingleton 不会 null,线程 2 就不会再次创建对象了。
至此我们就知道双重检查的必要性了。
双重检查懒汉单例模式真的就是完美的吗?
双重检查的饿汉单例模式看起来是很完美,实际上至少在 99.99% 的场景也不会出问题,但是它并不 100 完美,我们来回忆一下对象创建的过程,如下:
- 在 JVM 堆上开辟地址空间。
- 把开辟的地址空间赋值给 Java 虚拟机栈上的变量(到这里对象以及不为 null 了,因此完成了对象实例化)。
- 初始化对象,完成属性赋值。
总结来说就是对象先实例化后初始化,对象实例化之后因为在 JVM 堆上开辟了内存空间,因此就不会 null 了。
双重检查的饿汉单例模式的问题就出现在对象的实例化和初始化上,本质上对象的实例化和初始化没有什么问题,但是 JVM 又有一个指令重排的操作,具体来说就是对象分配了地址空间之后初始化之前,把对象的地址赋值给了静态成员变量 staticSingleton,而恰巧此时其他线程调用了 getInstance 方法,执行第一个 staticSingleton == null 判断时候就发现 staticSingleton 不为 null,就直接返回 staticSingleton 给调用者使用,结果可想而知,会发生空指针异常,那如何来解决这个问题呢?
volatile 关键字
一个变量如果被 volatile 关键字修饰后,就会具备一下两项能力:
- 保证线程间的可见性:当一个线程对共享变量进行了修改,其他线程可以立即看到修改后的最新值,volatile 能让行缓存无效,因此能读到内存中最新的值。
- 禁止进行指令重排序:用 volatile 修饰共享变量会在读、写共享变量时加入不同的屏障,阻止其他读写操作越过屏障,从而达到阻止重排序的效果。
上面我们谈到因为指令重排导致了 staticSingleton 对象已经不会 null 了,从而导致了调用者最终的空指针异常,我们了解了 volatile 关键字的作用后,对双重检查饿汉单例模式进行升级,代码如下:
public class DoubleCheckSingleton {//私有构造方法private DoubleCheckSingleton() {}//定义一个静态私有的成员变量private static volatile DoubleCheckSingleton staticSingleton;//对外提供静态方法获取静态单例对象 没有提前创建对象 因此是懒汉模式 使用 synchronized 进行加锁public static DoubleCheckSingleton getInstance() {//第一次为空判断if (staticSingleton == null) {//加锁synchronized (DoubleCheckSingleton.class) {//第二次为空判断if (staticSingleton == null) {staticSingleton = new DoubleCheckSingleton();}}}return staticSingleton;}}
饿汉单例模式–静态内部类
前面我们分享的静态相关的单例模式都是懒汉模式,而静态内部类的单例模式是饿汉的,代码如下:
public class StaticInnerClassSingleton {//私有构造方法private StaticInnerClassSingleton() {}//对外提供静态方法获取静态单例对象 没有提前创建对象 因此是懒汉模式 静态方法中调用静态内部内的静成员变量public static StaticInnerClassSingleton getInstance() {return SingletonHolder.STATIC_SINGLETON;}//静态内部类private static class SingletonHolder {//在内部类中创建一个 单例对象private static StaticInnerClassSingleton STATIC_SINGLETON = new StaticInnerClassSingleton();}}
可以看到静态内部类方式实现单例模式,最终对象的创建是由静态内部类创建的,因为 JVM 在加载外部类的过程中并不会去加载静态内部类,只有当静态内部类被的属性、方法被调用时候,才会进行初始化,可以看到我们在使用静态内部类实现恶汉单例模式并没有加锁,而且也是线程安全的,这表名静态内部类实现的单例模式是一种不错的选择。
饿汉单例模式–枚举
使用枚举类实现单例模式其实是最单例实现模式实现,因为枚举类型是线程安全的,并且只会装载一次,使用枚举实现单例模式充分的利用了枚举的特性,枚举的写法非常简单,代码如下:
public enum EumSingleton {INSTANCE;
}
枚举实现单量模式不仅简单,而且使用枚举实现单例模式是所用单例模式实现中唯一不会被破坏的单例实现模式(反射可以破坏单例模式)。
总结:本篇简单分享了单例模式的几种实现方式,并对各种单例模式的实现方式进行了剖析,希望可以帮助到有需要的朋友。
如有不正确的地方欢迎各位指出纠正。
相关文章:
设计模式--单例模式【创建型模式】
设计模式的分类 我们都知道有 23 种设计模式,这 23 种设计模式可分为如下三类: 创建型模式(5 种):单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。结构型模式(7 种)࿱…...
挑战一个月基本掌握C++(第七天)了解指针,引用,时间,输入输出,结构体,vector容器,数据结构 - 通用完结
一 指针 每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。 下面的实例,它将输出定义的变量地址: #include <iostream>using…...
百度面试手撕 go context channel部分学习
题目 手撕 对无序的切片查询指定数 使用context进行子协程的销毁 并且进行超时处理。 全局变量定义 var (startLoc int64(0) // --- 未处理切片数据起始位置endLoc int64(0) // --- 切片数据右边界 避免越界offset int64(0) // --- 根据切片和协程数量 在主线程 动态设…...
Spring事务管理详解
一、什么是事务管理 事务是一个最小的不可再分的工作单元。 一个事务对应一套完整的业务操作。事务管理是指这些操作要么全部成功执行,要么全部回滚,从而保证数据的一致性和完整性。比如银行转账,需要保证转出和转入是一个原子操作。Spring提…...
社区版 IDEA 开发webapp 配置tomcat
1.安装tomcat 参考Tomcat配置_tomcat怎么配置成功-CSDN博客 2.构建webapp项目结构 新建一个普通项目 然后添加webapp的目录结构: main目录下新建 webapp 文件夹 webapp文件夹下新建WEB_INF文件夹 *WEB_INF目录下新建web.xml wenapp文件夹下再新建index.html …...
打 印 菱 形
本题要求你写个程序打印成菱形的形状。例如给定17个符号,要求按下列格式打印 **** *********所谓“菱形形状”,是指每行输出奇数个符号;各行符号中心对齐;相邻两行符号数差2;符号数从1开始先从小到大顺序递增ÿ…...
ffmpeg翻页转场动效的安装及使用
文章目录 前言一、背景二、选型分析2.1 ffmpeg自带的xfade滤镜2.2 ffmpeg使用GL Transition库2.3 xfade-easing项目 三、安装3.1、安装依赖([参考](https://trac.ffmpeg.org/wiki/CompilationGuide/macOS#InstallingdependencieswithHomebrew))3.2、获取…...
[RocketMQ] 发送重试机制与消费重试机制~
发送重试 RocketMQ 客户端发送消息时,由于网络故障等因素导致消息发送失败,这时客户端SDK会触发重试机制,尝试重新发送以达到调用成功的效果。 触发条件 客户端消息发送请求失败或超时。服务端节点处于重启或下线状态。服务端运行慢造成请…...
基于Redis的网关鉴权方案与性能优化
文章目录 前言一、微服务鉴权1.1 前端权限检查1.2 后端权限检查1.3 优缺点 二、网关鉴权2.1 接口权限存储至Redis2.2 网关鉴权做匹配 总结 前言 在微服务架构中,如何通过网关鉴权结合Redis缓存提升权限控制的效率与性能。首先,文章对比了两种常见的权限…...
计算机网络-L2TP VPN基础概念与原理
一、概述 前面学习了GRE和IPSec VPN,今天继续学习另外一个也很常见的VPN类型-L2TP VPN。 L2TP(Layer 2 Tunneling Protocol) 协议结合了L2F协议和PPTP协议的优点,是IETF有关二层隧道协议的工业标准。L2TP是虚拟私有拨号网VPDN&…...
Node.js day-01
01.Node.js 讲解 什么是 Node.js,有什么用,为何能独立执行 JS 代码,演示安装和执行 JS 文件内代码 Node.js 是一个独立的 JavaScript 运行环境,能独立执行 JS 代码,因为这个特点,它可以用来编写服务器后端…...
vue el-dialog实现可拖拉
el-dialog实现拖拉,每次点击度居中显示,以下贴出代码具体实现,我是可以正常拖拉并且每次度显示在中间,效果还可以,需要的可以丢上去跑跑 组件部分: <el-dialog:visible.sync"dialogVisible"…...
go配置文件
https://github.com/spf13/viper viper golang中常用的配置文件工具为viper库,是一个第三方库。viper功能: 解析JSON、TOML、YAML、HCL等格式的配置文件。监听配置文件的变化(WatchConfig),不需要重启程序就可以读到最新的值。...
C++ OpenGL学习笔记(2、绘制橙色三角形绘制、绿色随时间变化的三角形绘制)
相关文章链接 C OpenGL学习笔记(1、Hello World空窗口程序) 目录 绘制橙色三角形绘制1、主要修改内容有:1.1、在主程序的基础上增加如下3个函数1.2、另外在主程序外面新增3个全局变量1.3、编写两个shader程序文件 2、initModel()函数3、initS…...
项目搭建+删除(单/批)
一 : 删除没有单独的页面,在列表页面写 二 : 删除在列表的页面 1.删除(单/双)的按钮 ① : 在列表文档就绪函数的ajax里面,成功回调函数追加数据里写删除按钮 注意点 : 删除/修改/回显都是根据id来的,记得传id ② : 批删给批删按钮,定义批删的方法 one : 示例(单删) : //循环追…...
《小米创业思考》
《小米创业思考》是小米创始人雷军对小米创业历程的系统梳理和深度思考,蕴含着许多宝贵的创业经验与智慧,以下是主要内容: 创业初心与梦想 - 源于热爱与使命感:雷军及团队怀着对科技的热爱和让每个人享受科技乐趣的使命感创立小米…...
多种注意力机制详解及其源码
✨✨ 欢迎大家来访Srlua的博文(づ ̄3 ̄)づ╭❤~✨✨ 🌟🌟 欢迎各位亲爱的读者,感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢,在这里我会分享我的知识和经验。&am…...
VMWare 的克隆操作
零、碎碎念 VMWare 的这个克隆操作很简单,单拎出来成贴的目的是方便后续使用。 一、操作步骤 1.1、在“源”服务器上点右键,选择“管理--克隆” 1.2、选择“虚拟机的当前状态”为基础制作克隆,如下图所示,然后点击“下一页” 1.3、…...
Y3编辑器教程7:界面编辑器
文章目录 一、简介1.1 导航栏1.2 画板1.3 场景界面1.4 控件1.4.1 空节点1.4.2 按钮1.4.3 图片1.4.4 模型1.4.5 文本1.4.6 输入框1.4.7 进度条1.4.8 列表 1.5 元件1.5.1 简介1.5.2 差异说明1.5.3 元件实例的覆盖、还原与禁止操作1.5.4 迷雾控件 1.6 属性1.7 事件(动画…...
「Mac畅玩鸿蒙与硬件45」UI互动应用篇22 - 评分统计工具
本篇将带你实现一个评分统计工具,用户可以对多个选项进行评分。应用会实时更新每个选项的评分结果,并统计平均分。这一功能适合用于问卷调查或评分统计的场景。 关键词 UI互动应用评分统计状态管理数据处理多目标评分 一、功能说明 评分统计工具允许用…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
raid存储技术
1. 存储技术概念 数据存储架构是对数据存储方式、存储设备及相关组件的组织和规划,涵盖存储系统的布局、数据存储策略等,它明确数据如何存储、管理与访问,为数据的安全、高效使用提供支撑。 由计算机中一组存储设备、控制部件和管理信息调度的…...
