简谈设计模式之单例模式
上一篇博客已经介绍了设计模式及其设计原则, 在这篇博客中笔者会介绍一下单例模式, 也是最简单的一种设计模式
单例模式
单例模式属于创建型模式. 它涉及到一个单一的类, 该类负责创建自己的对象, 同时确保只有单个对象被创建, 这个类提供了一种访问其唯一对象的方式, 可以直接访问, 不需要实例化这个类的对象
单例模式结构
- 单例类. 只能创建一个实例的类
- 访问类. 使用单例类
单例模式实现
- 饿汉式单例
特点: 在类加载时就创建实例, 线程安全, 但是可能会导致资源浪费
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、存储结构: 链式存储 链表就是将 结点 用链串起来的线性表,链就是 结点 中的…...

单片机软件架构连载(4)-结构体
枚举、指针、结构体,我愿称为C语言"三板斧"。 用人话来讲,几乎所有c语言高阶编程,都离不开这这3个知识点的应用。 今天站在实际产品常用的角度,给大家讲一下结构体。 1.结构体概念 结构体可以用来构建更复杂的数据结…...

工厂方法模式在金融业务中的应用及其框架实现
引言 工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它定义了一个创建对象的接口,但由子类决定实例化哪一个类。工厂方法模式使得类的实例化延迟到子类。在金融业务中,工厂方法模式可以用于创建不同类型…...

python库(6):Pygments库
1 Pygments介绍 在软件开发和文档编写中,代码的可读性是至关重要的一环。无论是在博客文章、技术文档还是教程中,通过代码高亮可以使程序代码更加清晰和易于理解。而在Python世界中,Pygments库就是这样一个强大的工具,它能够将各…...

金斗云 HKMP智慧商业软件 任意用户创建漏洞复现
0x01 产品简介 金斗云智慧商业软件是一款功能强大、易于使用的智慧管理系统,通过智能化的管理工具,帮助企业实现高效经营、优化流程、降低成本,并提升客户体验。无论是珠宝门店、4S店还是其他零售、服务行业,金斗云都能提供量身定制的解决方案,助力企业实现数字化转型和智…...

前端JS特效第24集:jquery css3实现瀑布流照片墙特效
jquery css3实现瀑布流照片墙特效,先来看看效果: 部分核心的代码如下(全部代码在文章末尾): <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8" /> <title>jquerycss3实现瀑…...

区块链论文速读A会-ISSTA 2023(2/2)如何检测DeFi协议中的价格操纵漏洞
Conference:ACM SIGSOFT International Symposium on Software Testing and Analysis (ISSTA) CCF level:CCF A Categories:Software Engineering/System Software/Programming Languages Year:2023 第1~5篇区块链文章 请点击此…...

权力之望怎么下载客户端 权力之望一键下载
《权力之望》是一款由NX3 Games开发、Smilegate发行的多人在线动作MMORPG游戏。这款游戏最大的特点是高度的自由度和丰富的角色定制选项。我们在游戏中不仅可以自由更换武器,而且游戏还提供了54种能力和60多种职业选择,让我们可以根据自己的游戏风格和喜…...

Oracle PL/SQL 循环批量执行存储过程
1. 查询存储过程 根据数据字典USER_OBJECTS查询出所有存储过程。 2. 动态拼接字符串(参数等) 根据数据字典USER_ARGUMENTS动态拼接参数。 3. 动态执行 利用EXECUTE IMMEDIATE动态执行无名块。 4. 输出执行信息 利用DBMS_OUTPUT.PUT_LINE输出执行成功与…...

kafka 生产者
生产者 生产者负责创建消息,然后将其投递到Kafka中。 负载均衡 轮询策略。随机策略。按照 key 进行hash。 Kafka 的默认分区策略:如果指定了 key,key 相同的消息会发送到同一个分区(分区有序);如果没有…...

Powershell 获取电脑保存的所有wifi密码
一. 知识点 netsh wlan show profiles 用于显示计算机上已保存的无线网络配置文件 Measure-Object 用于统计数量 [PSCustomObject]{ } 用于创建Powershell对象 [math]::Round 四舍五入 Write-Progress 显示进度条 二. 代码 只能获取中文Windows操作系统的wifi密码如果想获取…...