简谈设计模式之单例模式
上一篇博客已经介绍了设计模式及其设计原则, 在这篇博客中笔者会介绍一下单例模式, 也是最简单的一种设计模式
单例模式
单例模式属于创建型模式. 它涉及到一个单一的类, 该类负责创建自己的对象, 同时确保只有单个对象被创建, 这个类提供了一种访问其唯一对象的方式, 可以直接访问, 不需要实例化这个类的对象
单例模式结构
- 单例类. 只能创建一个实例的类
- 访问类. 使用单例类
单例模式实现
- 饿汉式单例
特点: 在类加载时就创建实例, 线程安全, 但是可能会导致资源浪费
public class Singleton {// 在类加载时就创建实例instanceprivate static final Singleton instance = new Singleton();// 私有的构造函数, 避免从外部构造新实例private Singleton() {}// 提供一个全局访问的接口, 可以获取已经创建好的单个实例instancepublic static Singleton getInstance() {return instance;}
}
- 懒汉式单例
特点: 延迟创建实例, 但是线程不安全
public class Singleton {// 单个的实例private static Singleton instance;// 私有的构造函数, 避免从外部构造新实例private Singleton() {}// 提供一个全局访问的接口, 可以获取已经创建好的单个实例instancepublic static Singleton getInstance() {// 假如这时instance还没有被创建, 那么就创建一个新的实例instanceif (instace == null) {instance = new Singleton();}return instance;}
}
懒汉式单例模式在多线程环境下容易导致线程不安全, 这是因为多个线程可能会同时访问 getInstance() 方法并且同时进入 if (instance == null) 代码块, 这样就会创建多个实例, 违背了单例模式的原则.
- 线程安全的懒汉式单例
特点: 延迟创建实例, 使用同步方法保证线程安全, 但是会有性能开销
public class Singleton {// 单个的实例private static Singleton instance;// 私有的构造函数, 避免从外部构造新实例private Singleton() {}// 提供一个全局访问的接口, 可以获取已经创建好的单个实例instance, 使用同步方法保证线程安全public static synchronized Singleton getInstance() {// 假如这时instance还没有被创建, 那么就创建一个新的实例instanceif (instace == null) {instance = new Singleton();}return instance;}
}
- 双重检查锁
特点: 提高性能, 减少同步开销, 线程安全
public class Singleton {// 单个的实例private static Singleton instance;// 私有的构造函数, 避免从外部构造新实例private Singleton() {}// 提供一个全局访问的接口, 可以获取已经创建好的单个实例instancepublic static Singleton getInstance() {// 第一次判断实例是否为null, 如果不为null就直接返回实例, 不进入抢锁阶段if (instance == null) {synchronized (Singleton.class) {// 抢到锁了再判断一次是否为nullif (instance == null) {instance = new Singleton();}}}return instance;}
}
双重检查锁模式可能会出现空指针问题, 出现问题的原因是JVM在实例对象时会进行优化和指令重排序操作
为了解决空指针异常问题, 可以使用 volatile 关键字, volatile 关键字可以保证可见性和有序性
public class Singleton {// 单个的实例, 使用volatile关键字保证其可见性和有序性private static volatile Singleton instance;// 私有的构造函数, 避免从外部构造新实例private Singleton() {}// 提供一个全局访问的接口, 可以获取已经创建好的单个实例instancepublic static Singleton getInstance() {// 第一次判断实例是否为null, 如果不为null就直接返回实例, 不进入抢锁阶段if (instance == null) {synchronized (Singleton.class) {// 抢到锁了再判断一次是否为nullif (instance == null) {instance = new Singleton();}}}return instance;}
}
笔者写到这一段的时候突然想到, 如果把上面双重检查锁的代码略改一下, 改成下面这样, 是否可行?
// Double-Checked Locking version 1
public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {instance = new Singleton();}}return instance;
}
//======================================
// Double-Checked Locking version 2
public static Singleton getInstance() {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}return instance;
}
上面的两种改法, 分别是把synchronized同步块内和同步块外的判断语句 if (instance == null) 删掉之后得到的新代码.
上面这两种改法是否可行呢? 其实都不好. 对于版本1, 假设有线程1和线程2, 进行了如下操作
---------------------------------------------------------Thread 1 Thread 2| || || || |
走到synchronized代码块处 |
拿到锁之后发生了一次线程切换 || || 走到synchronized代码块处, 拿不到锁, 被阻塞| 线程切换| |
Thread 1创建了一个新实例 |
Thread 1离开了synchronized代码块 |
锁被释放 |
线程切换 || || || Thread 2拿到锁| Thread 2创建了新实例 (这里违背了单例模式原则)| Thread 2离开了synchronized代码块| Thread 2返回了创建的实例| 线程切换| |
Thread 1返回创建的实例 |
---------------------------------------------------------
这就和线程不安全的懒汉单例模式一样了
对于版本2, 其实和使用同步代码块的懒汉单例模式也是一样的, 线程是安全的, 但是性能开销依然存在
- 静态内部类
特点: 利用类加载机制实现懒加载, 线程安全
public class Singleton {// 私有的构造函数, 避免从外部构造新实例private Singleton() {}// 静态内部类, 延迟加载private static class SingletonHelper {private static final Singleton INSTANCE = new Singleton();}// 提供一个全局访问的接口, 可以获取已经创建好的单个实例instancepublic static Singleton getInstance() {return SingletonHelper.INSTANCE;}
}
- 枚举单例
特点: 简单, 线程安全, 防止反序列化导致创建新的实例
public enum Singleton {INSTANCE;// 其他方法public void someMethod() {// do something}
}
单例模式被破坏的情况
除了枚举单例模式之外, 其他单例模式都可以被破坏. 破坏单例模式的方法有两种, 分别为 序列化 和 反射
- 序列化破坏单例模式
因为在序列化和反序列化过程中, 会创建一个新的实例, 即使单例类在内存中有一个唯一的实例, 通过反序列化也能创建多个实例, 这样就破坏了单例模式的初衷
假设有一个单例类如下:
import java.io.Serializablepublic class Singleton implements Serializable {private static final long serialVersionUID = 1L;private static final Singleton instance = new instance();private Singleton();public Singleton getInstance() {return instance;}// other methods...
}
破坏单例模式的场景
import java.io.*;public class SingletonDemo {public static void main(String[] args) {try {Singleton instance1 = Singleton.getInstance();// 序列化ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("singleton.ser"));out.writeObject(instance1);out.close();// 反序列化ObjectInputStream in = new ObjectInputStream(new FileInputStream("singleton.ser"));Singleton instance2 = (Singleton) in.readObject();in.close;System.out.println("Instance 1 hash code: " + instance1.hashCode());System.out.println("Instance 2 hash code: " + instance2.hashCode());} catch (Exception e) {e.printStackTrace();}}
}
运行 SingletonDemo, 发现 instance1 和 instance2 的哈希码并不相同, 说明它们是不同的实例, 这就破坏了单例模式
为了防止序列化破坏单例模式, 可以在单例类中定义 readResolve 方法, 这个方法在反序列化时会被调用, 返回当前的单例实例, 从而确保反序列化得到的始终是唯一的单例实例
改进之后的单例类
import java.io.Serializablepublic class Singleton implements Serializable {private static final long serialVersionUID = 1L;private static final Singleton instance = new instance();private Singleton();public Singleton getInstance() {return instance;}// 添加readResolve方法protected Object readResolve() {return getInstance();}// other methods...
}
再次运行 SingletonDemo , 发现 instance1 和 instance2 的哈希码是相同的, 因此它们是同一个实例, 单例模式没有被破坏.
- 反射破坏单例模式
因为反射允许我们访问私有构造方法, 从而构建多个对象, 这就违背了单例模式的初衷
假设有一个单例类如下:
public class Singleton {private static final Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}
}
破坏单例模式的场景
import java.lang.reflect.Constructor;public class SingletonDemo {public static void main(String[] args) {try {Singleton instance1 = Singleton.getInstance();// 通过反射创建新的实例Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();constructor.setAccessible(true);Singleton instance2 = constructor.newInstance();// 检查两个实例是否相同System.out.println("Instance 1 hash code: " + instance1.hashCode());System.out.println("Instance 2 hash code: " + instance2.hashCode());} catch (Exception e) {e.printStackTrace();}}
}
运行 SingletonDemo, 发现 instance1 和 instance2 的哈希码并不相同. 说明它们是不同的实例, 单例模式被破坏
为了防止反射破坏单例模式, 可以在构造方法中添加防御措施, 例如在构造方法中检测实例是否存在, 如果存在就抛出异常
改进之后的单例类
public class Singleton {private static final Singleton instance = new Singleton();private Singleton() {// 防止反射创建新的实例if (instance != null) {throw new RuntimeException("Use getInstance() method to get the single instance of this class.");}}public static Singleton getInstance() {return instance;}
}
再次运行 SingletonDemo, 发现反射创建实例的步骤会抛出异常, 阻止了反射破坏单例模式
最近在学设计模式, 可能会高强度更新设计模式相关的技术博客. 对设计模式感兴趣的读者可以关注我的 CSDN Channel, 掘金 Channel, 我的个人博客网站 或 网站的镜像站点
相关文章:
简谈设计模式之单例模式
上一篇博客已经介绍了设计模式及其设计原则, 在这篇博客中笔者会介绍一下单例模式, 也是最简单的一种设计模式 单例模式 单例模式属于创建型模式. 它涉及到一个单一的类, 该类负责创建自己的对象, 同时确保只有单个对象被创建, 这个类提供了一种访问其唯一对象的方式, 可以直…...
在Spring Boot中实现多线程任务调度
在Spring Boot中实现多线程任务调度 大家好,我是微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿! 1. Spring Boot中的任务调度 Spring Boot通过集成Spring框架的Task Execution和Scheduling支持,提供…...
dify/api/models/account.py文件中的数据表
源码位置:dify\api\models\account.py accounts 表结构 字段英文名数据类型字段中文名字备注idStringUUIDIDnameString名称emailString邮箱passwordString密码password_saltString密码盐avatarString头像interface_languageString界面语言interface_themeString界…...
SQLAlchemy迁移数据库
SQLAlchemy迁移数据库 目录 SQLAlchemy迁移数据库安装Alembic配置Alembic编辑 alembic.ini编辑env.py生成迁移文件建表语句示例修改迁移文件命名格式 安装Alembic pip install alembic配置Alembic 执行初始化后会创建一个 alembic 目录,包含Alembic的配置文件 ale…...
Django文档简化版——Django快速入门——创建一个基本的投票应用程序
Django快速入门——创建一个基本的投票应用程序 准备工作1、创建虚拟环境2、安装django 1、请求和响应(1)创建项目(2)用于开发的简易服务器(3)创建投票应用(4)编写第一个视图1、编写…...
安全防御第三天(笔记持续更新)
1.接口类型以及作用 接口 --- 物理接口 三层口 --- 可以配置IP地址的接口 二层口 普通二层口 接口对 --- “透明网线” --- 可以将一个或者两个接口配置成为接口对,则 数据从一个接口进,将不需要查看MAC地址表,直接从另一个接口出;…...
【12321骚扰电话举报受理中心-短信验证安全分析报告】
前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞…...
杂项——循迹模块调节方法
1-4 路灰度传感器的调节方法: 调节时探头应对着颜色较浅的上方(如果是黑白线赛道则应该将探头 对着白色上方调),轻轻的将全部可调电阻顺时针拧到底,再逆时针 慢慢回旋,直到对应探头的信号指示灯亮起后&…...
揭秘:源代码防泄密的终极秘籍
在当今信息科技高度发达的时代,源代码作为企业最核心的资产之一,其安全性不言而喻。源代码的泄露可能导致企业技术机密被竞争对手获取,进而威胁到企业的市场竞争力和长远发展。因此,源代码防泄密成为了企业信息安全工作的重中之重…...
avcodec_send_packet函数阻塞
用ffmpeg4.1.4开发一个播放器,解码过程如下,在每个函数前设置标志,测试发现程序阻塞在avcodec_send_packet函数。 while(true){av_read_frameavcodec_send_packetavcodec_receive_frameav_packet_unref } 解释如下: avcodec_se…...
一个parquet-go例子
一个parquet-go例子 使用go读写parquet,使用到了框架github.com/xitongsys/parquet-go 代码: package mainimport ("log""time""github.com/xitongsys/parquet-go-source/local""github.com/xitongsys/parquet-go/parquet&qu…...
扩散模型笔记
长参数“T”决定了生成全噪声图像所需的步长。在本文中,该参数被设置为1000,这可能显得很大。我们真的需要为数据集中的每个原始图像创建1000个噪声图像吗?马尔可夫链方面被证明有助于解决这个问题。由于我们只需要上一步的图像来预测下一步,…...
上海-LM科技(面经)
上海-LM科技 hr电话面 个人简介 个人信息的询问 是否知道芋道框架 技术面 算法题 14. 最长公共前缀(写出来即可) 聊一下Docker Docker核心概念总结Docker实战 聊一下AOP Spring AOP详解 聊一下JWT JWT 基础概念详解JWT 身份认证优缺点分析 Spring…...
用 Echarts 画折线图
https://andi.cn/page/621503.html...
C++的map / multimap容器
一、介绍 在C的map / multimap容器中,所有的元素均是pair类型(有关pair类型可以参考我之前写的 《C的set / multiset容器》的3.2中有介绍到)。 每对pair的第一个元素被称为关键字key,第二个元素被称为值value。因此,ma…...
双向链表 -- 详细理解和实现
欢迎光顾我的homepage 前言 双向链表是一种带头双向循环的链表。在双向链表中,首先存在着一个头结点;其次每个节点有指向下一个节点的指针next 和指向上一个节点的指针prev ;…...
WebGIS面试题
文章目录 1. 前端1.1. 选择器的优先级1.2. CSS 中它的布局有哪些?1.3. CSS3 的新特性1.4. CSS 的两种盒子模型1.5. CSS 的伪元素选择器和伪类选择器有哪些?1.6. ES6 的新特性1.7. 谈谈你对 promise 的理解1.8. 简单说一下原型链1.9. 简单说一下深浅拷贝1…...
代码随想录算法训练营:21/60
非科班学习算法day21 | LeetCode669:修剪二叉搜索树 ,Leetcode108:将有序数组转换为二叉搜索树 ,Leetcode538:把二叉搜索树转换为累加树 介绍 包含LC的两道题目,还有相应概念的补充。 相关图解和更多版本: 代码随想录 (progra…...
数据结构——二叉树之c语言实现堆与堆排序
目录 前言: 1.二叉树的概念及结构 1.1 特殊的二叉树 1.2 二叉树的存储结构 1.顺序存储 2.链式存储 2. 二叉树的顺序结构及实现 2.1 堆的概念 编辑 2.2 堆的创建 3.堆的实现 3.1 堆的初始化和销毁 初始化: 销毁: 插入&…...
#数据结构 链表
单向链表 1. 概念 单向链表 单向循环链表 双向链表 双向循环链表 解决:长度固定的问题,插入和删除麻烦的问题 1、逻辑结构: 线性结构 2、存储结构: 链式存储 链表就是将 结点 用链串起来的线性表,链就是 结点 中的…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...
处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的
修改bug思路: 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑:async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...
mac:大模型系列测试
0 MAC 前几天经过学生优惠以及国补17K入手了mac studio,然后这两天亲自测试其模型行运用能力如何,是否支持微调、推理速度等能力。下面进入正文。 1 mac 与 unsloth 按照下面的进行安装以及测试,是可以跑通文章里面的代码。训练速度也是很快的。 注意…...
MFE(微前端) Module Federation:Webpack.config.js文件中每个属性的含义解释
以Module Federation 插件详为例,Webpack.config.js它可能的配置和含义如下: 前言 Module Federation 的Webpack.config.js核心配置包括: name filename(定义应用标识) remotes(引用远程模块࿰…...
comfyui 工作流中 图生视频 如何增加视频的长度到5秒
comfyUI 工作流怎么可以生成更长的视频。除了硬件显存要求之外还有别的方法吗? 在ComfyUI中实现图生视频并延长到5秒,需要结合多个扩展和技巧。以下是完整解决方案: 核心工作流配置(24fps下5秒120帧) #mermaid-svg-yP…...
在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例
目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码:冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...
