当前位置: 首页 > news >正文

设计模式--单例模式【创建型模式】

设计模式的分类

我们都知道有 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. 假设有线程 1 和现成 2 都来调用 getInstance 方法获取单例对象,进入第一个 staticSingleton == null 的判断时候,线程 1 和线程 2 都满足条件,都可以继续往下执行。
  2. 接着线程 1 获取到锁,再次进行 staticSingleton == null 的判断,此时 staticSingleton 任然为 null,线程 1 就会开始创建对象,线程 2 等待获取锁。
  3. 线程 1 创建完对象后释放锁,线程 2 获取到锁后来执行 staticSingleton == null 判断,因为线程 1 已经创建了对象,staticSingleton 不会 null,线程 2 就不会再次创建对象了。

至此我们就知道双重检查的必要性了。

双重检查懒汉单例模式真的就是完美的吗?

双重检查的饿汉单例模式看起来是很完美,实际上至少在 99.99% 的场景也不会出问题,但是它并不 100 完美,我们来回忆一下对象创建的过程,如下:

  1. 在 JVM 堆上开辟地址空间。
  2. 把开辟的地址空间赋值给 Java 虚拟机栈上的变量(到这里对象以及不为 null 了,因此完成了对象实例化)。
  3. 初始化对象,完成属性赋值。

总结来说就是对象先实例化后初始化,对象实例化之后因为在 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 种)&#xff1…...

挑战一个月基本掌握C++(第七天)了解指针,引用,时间,输入输出,结构体,vector容器,数据结构 - 通用完结

一 指针 每一个变量都有一个内存位置&#xff0c;每一个内存位置都定义了可使用连字号&#xff08;&&#xff09;运算符访问的地址&#xff0c;它表示了在内存中的一个地址。 下面的实例&#xff0c;它将输出定义的变量地址&#xff1a; #include <iostream>using…...

百度面试手撕 go context channel部分学习

题目 手撕 对无序的切片查询指定数 使用context进行子协程的销毁 并且进行超时处理。 全局变量定义 var (startLoc int64(0) // --- 未处理切片数据起始位置endLoc int64(0) // --- 切片数据右边界 避免越界offset int64(0) // --- 根据切片和协程数量 在主线程 动态设…...

Spring事务管理详解

一、什么是事务管理 事务是一个最小的不可再分的工作单元。 一个事务对应一套完整的业务操作。事务管理是指这些操作要么全部成功执行&#xff0c;要么全部回滚&#xff0c;从而保证数据的一致性和完整性。比如银行转账&#xff0c;需要保证转出和转入是一个原子操作。Spring提…...

社区版 IDEA 开发webapp 配置tomcat

1.安装tomcat 参考Tomcat配置_tomcat怎么配置成功-CSDN博客 2.构建webapp项目结构 新建一个普通项目 然后添加webapp的目录结构&#xff1a; main目录下新建 webapp 文件夹 webapp文件夹下新建WEB_INF文件夹 *WEB_INF目录下新建web.xml wenapp文件夹下再新建index.html …...

打 印 菱 形

本题要求你写个程序打印成菱形的形状。例如给定17个符号&#xff0c;要求按下列格式打印 **** *********所谓“菱形形状”&#xff0c;是指每行输出奇数个符号&#xff1b;各行符号中心对齐&#xff1b;相邻两行符号数差2&#xff1b;符号数从1开始先从小到大顺序递增&#xff…...

ffmpeg翻页转场动效的安装及使用

文章目录 前言一、背景二、选型分析2.1 ffmpeg自带的xfade滤镜2.2 ffmpeg使用GL Transition库2.3 xfade-easing项目 三、安装3.1、安装依赖&#xff08;[参考](https://trac.ffmpeg.org/wiki/CompilationGuide/macOS#InstallingdependencieswithHomebrew)&#xff09;3.2、获取…...

[RocketMQ] 发送重试机制与消费重试机制~

发送重试 RocketMQ 客户端发送消息时&#xff0c;由于网络故障等因素导致消息发送失败&#xff0c;这时客户端SDK会触发重试机制&#xff0c;尝试重新发送以达到调用成功的效果。 触发条件 客户端消息发送请求失败或超时。服务端节点处于重启或下线状态。服务端运行慢造成请…...

基于Redis的网关鉴权方案与性能优化

文章目录 前言一、微服务鉴权1.1 前端权限检查1.2 后端权限检查1.3 优缺点 二、网关鉴权2.1 接口权限存储至Redis2.2 网关鉴权做匹配 总结 前言 在微服务架构中&#xff0c;如何通过网关鉴权结合Redis缓存提升权限控制的效率与性能。首先&#xff0c;文章对比了两种常见的权限…...

计算机网络-L2TP VPN基础概念与原理

一、概述 前面学习了GRE和IPSec VPN&#xff0c;今天继续学习另外一个也很常见的VPN类型-L2TP VPN。 L2TP&#xff08;Layer 2 Tunneling Protocol&#xff09; 协议结合了L2F协议和PPTP协议的优点&#xff0c;是IETF有关二层隧道协议的工业标准。L2TP是虚拟私有拨号网VPDN&…...

Node.js day-01

01.Node.js 讲解 什么是 Node.js&#xff0c;有什么用&#xff0c;为何能独立执行 JS 代码&#xff0c;演示安装和执行 JS 文件内代码 Node.js 是一个独立的 JavaScript 运行环境&#xff0c;能独立执行 JS 代码&#xff0c;因为这个特点&#xff0c;它可以用来编写服务器后端…...

vue el-dialog实现可拖拉

el-dialog实现拖拉&#xff0c;每次点击度居中显示&#xff0c;以下贴出代码具体实现&#xff0c;我是可以正常拖拉并且每次度显示在中间&#xff0c;效果还可以&#xff0c;需要的可以丢上去跑跑 组件部分&#xff1a; <el-dialog:visible.sync"dialogVisible"…...

go配置文件

https://github.com/spf13/viper viper golang中常用的配置文件工具为viper库&#xff0c;是一个第三方库。viper功能&#xff1a; 解析JSON、TOML、YAML、HCL等格式的配置文件。监听配置文件的变化(WatchConfig)&#xff0c;不需要重启程序就可以读到最新的值。...

C++ OpenGL学习笔记(2、绘制橙色三角形绘制、绿色随时间变化的三角形绘制)

相关文章链接 C OpenGL学习笔记&#xff08;1、Hello World空窗口程序&#xff09; 目录 绘制橙色三角形绘制1、主要修改内容有&#xff1a;1.1、在主程序的基础上增加如下3个函数1.2、另外在主程序外面新增3个全局变量1.3、编写两个shader程序文件 2、initModel()函数3、initS…...

项目搭建+删除(单/批)

一 : 删除没有单独的页面,在列表页面写 二 : 删除在列表的页面 1.删除(单/双)的按钮 ① : 在列表文档就绪函数的ajax里面,成功回调函数追加数据里写删除按钮 注意点 : 删除/修改/回显都是根据id来的,记得传id ② : 批删给批删按钮,定义批删的方法 one : 示例(单删) : //循环追…...

《小米创业思考》

《小米创业思考》是小米创始人雷军对小米创业历程的系统梳理和深度思考&#xff0c;蕴含着许多宝贵的创业经验与智慧&#xff0c;以下是主要内容&#xff1a; 创业初心与梦想 - 源于热爱与使命感&#xff1a;雷军及团队怀着对科技的热爱和让每个人享受科技乐趣的使命感创立小米…...

多种注意力机制详解及其源码

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…...

VMWare 的克隆操作

零、碎碎念 VMWare 的这个克隆操作很简单&#xff0c;单拎出来成贴的目的是方便后续使用。 一、操作步骤 1.1、在“源”服务器上点右键&#xff0c;选择“管理--克隆” 1.2、选择“虚拟机的当前状态”为基础制作克隆&#xff0c;如下图所示&#xff0c;然后点击“下一页” 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 事件&#xff08;动画…...

「Mac畅玩鸿蒙与硬件45」UI互动应用篇22 - 评分统计工具

本篇将带你实现一个评分统计工具&#xff0c;用户可以对多个选项进行评分。应用会实时更新每个选项的评分结果&#xff0c;并统计平均分。这一功能适合用于问卷调查或评分统计的场景。 关键词 UI互动应用评分统计状态管理数据处理多目标评分 一、功能说明 评分统计工具允许用…...

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇&#xff0c;在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下&#xff1a; 【Note】&#xff1a;如果你已经完成安装等操作&#xff0c;可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作&#xff0c;重…...

[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解

突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 ​安全措施依赖问题​ GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者写过很多次这道题了&#xff0c;不想写题解了&#xff0c;大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

服务器--宝塔命令

一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行&#xff01; sudo su - 1. CentOS 系统&#xff1a; yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...

【从零学习JVM|第三篇】类的生命周期(高频面试题)

前言&#xff1a; 在Java编程中&#xff0c;类的生命周期是指类从被加载到内存中开始&#xff0c;到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期&#xff0c;让读者对此有深刻印象。 目录 ​…...

解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用

在工业制造领域&#xff0c;无损检测&#xff08;NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统&#xff0c;以非接触式光学麦克风技术为核心&#xff0c;打破传统检测瓶颈&#xff0c;为半导体、航空航天、汽车制造等行业提供了高灵敏…...

通过 Ansible 在 Windows 2022 上安装 IIS Web 服务器

拓扑结构 这是一个用于通过 Ansible 部署 IIS Web 服务器的实验室拓扑。 前提条件&#xff1a; 在被管理的节点上安装WinRm 准备一张自签名的证书 开放防火墙入站tcp 5985 5986端口 准备自签名证书 PS C:\Users\azureuser> $cert New-SelfSignedCertificate -DnsName &…...

永磁同步电机无速度算法--基于卡尔曼滤波器的滑模观测器

一、原理介绍 传统滑模观测器采用如下结构&#xff1a; 传统SMO中LPF会带来相位延迟和幅值衰减&#xff0c;并且需要额外的相位补偿。 采用扩展卡尔曼滤波器代替常用低通滤波器(LPF)&#xff0c;可以去除高次谐波&#xff0c;并且不用相位补偿就可以获得一个误差较小的转子位…...

从零开始了解数据采集(二十八)——制造业数字孪生

近年来&#xff0c;我国的工业领域正经历一场前所未有的数字化变革&#xff0c;从“双碳目标”到工业互联网平台的推广&#xff0c;国家政策和市场需求共同推动了制造业的升级。在这场变革中&#xff0c;数字孪生技术成为备受关注的关键工具&#xff0c;它不仅让企业“看见”设…...

CSS3相关知识点

CSS3相关知识点 CSS3私有前缀私有前缀私有前缀存在的意义常见浏览器的私有前缀 CSS3基本语法CSS3 新增长度单位CSS3 新增颜色设置方式CSS3 新增选择器CSS3 新增盒模型相关属性box-sizing 怪异盒模型resize调整盒子大小box-shadow 盒子阴影opacity 不透明度 CSS3 新增背景属性ba…...