2023.10.19 关于设计模式 —— 单例模式
目录
引言
单例模式
饿汉模式
懒汉模式
懒汉模式线程安全问题
分析原因
引言
- 设计模式为编写代码的 约定 和 规范
阅读下面文章前建议点击下方链接明白 对象 和 类对象
对象和类对象
单例模式
- 单个实例(对象)
- 在某些场景中有特定的类,其只能被创建出一个实例,不应该被创建多个实例
- 而 单例模式 就针对上述的需求场景进行更强制的保证
- 通过巧用 java 的现有语法,实现了某个类只能被创建出一个实例的效果
- 从而当程序员不小心创建出多个实例时,会编译报错
实例理解:
- JDBC 编程中的 DataSource 类,我们仅连接一个数据库时
- DataSource 类描述数据的来源,用于获取数据库连接,因为数据只来源于一个数据库,所以我们仅创建一个实例即可,无需创建多个实例
- 从而该场景适合使用单例模式
具体思路:
- static 关键字可以将成员变量或方法声明为静态的,让它们属于类级别而不是实例级别
- 因此我们可以将 单例对象 声明为静态变量,让其可以在任何地方直接通过类名来访问,无需创建类的实例
- 从而这样便可以方便地获取单例对象,并且对于多个调用者来说,始终返回同一个实例
简单理解:
- static 关键字将 单例对象 转为 静态变量
- 因此 单例对象 便从 与实例相关联 转为 与类相关联
- 又因为 在一个 java 进程中,对于同一个类,只会存在一个对应的类对象
- 所以该 类对象内部的类属性也仅会存在一份,也就是作为类属性的 单例对象 也仅会存在一份
饿汉模式
// 饿汉模式的 单例模式 实现 // 此处保证 Singleton 这个类只能创建出一个实例 class Singleton { // 在此处,先把这个实例给创建出来了 // 使用 private 修饰是为了防止在类外对 Singleton 实例 instance 进行修改private static Singleton instance = new Singleton();// 如果需要使用这个唯一实例,统一通过 Singleton.getInstance() 方式来获取public static Singleton getInstance() {return instance;}// 为了避免 Singleton 类不小心被复制出来多份 // 把构造方法设为 private ,在类外面就无法通过 new 的方式来创建这个 Singleton 实例了private Singleton() {} }public class ThreadDemo19 {public static void main(String[] args) {Singleton s = Singleton.getInstance();Singleton s2 = Singleton.getInstance();System.out.println(s == s2);} }
- 该模式表示在类加载阶段,就已经把实例创建出来了
- 所以 "饿汉" 一词便体现出创建该实例的急迫感
懒汉模式
class SingletonLazy {private static SingletonLazy instance = null;public static SingletonLazy getInstance() {if(instance == null) {instance = new SingletonLazy();}return instance;}private SingletonLazy(){} }public class ThreadDemo20 {public static void main(String[] args) {SingletonLazy s1 = SingletonLazy.getInstance();SingletonLazy s2 = SingletonLazy.getInstance();System.out.println(s1 == s2);} }
- 该模式在创建实例时并非是在类加载阶段,就已经把实例创建出来了
- 而是当真正第一次使用的时候才创建实例
- 所以相比于 "饿汉" 模式创建实例的急切感,"懒汉" 模式则显得没那么着急
阅读下面文章之前建议点击下方链接了解清楚线程安全问题
线程安全问题详解
懒汉模式线程安全问题
- 相比于 饿汉模式 仅涉及到读操作
- 懒汉模式 则既涉及到 写操作 又涉及到 读操作
- 显然 懒汉模式 有着线程安全问题
分析原因
- 懒汉模式线程安全问题的本质为 读操作、比较操作、写操作 这三个操作并不是原子的
- 从而便会导致线程t2 读到的 instance 值可能是线程t1 还没来得及写的
- 这也就是我们常说的 脏读
- 此时我们便可以利用 synchronized 关键字来进行加锁,使得上图中的指令变为原子的
public static SingletonLazy getInstance() {synchronized (SingletonLazy.class) {if(instance == null) {instance = new SingletonLazy();}}return instance;}
- 加锁的对象是 SingletonLazy.class 类对象
- 该锁是基于类的
- 虽然对 SingletonLazy.class 类对象进行加锁能解决多线程之间脏读的问题
- 但是也导致了每次调用 getInstance 方法时都需要先进行加锁,才能进入方法内部进行判断 instance 是否为空,非空则触发 return 直接返回单例对象
- 我们要清楚的一点是 加锁操的开销还挺大,会涉及到用户态到内核态之间的切换,这样切换成本的成本是很高的
- 要注意到的是 在 new 完单例对象之后,后续再调用 getInstance 方法时,我们仅会直接返回单例对象,即仅涉及到读操作,这是没有线程安全问题的
- 所以在 new 出对象之前有加锁操作,这是十分有必要的,即任意线程第一次调用getInstance 方法
- 在 new 完单例对象之后,我们无需再进行加锁操作,这样便可以很大程度上提高效率
public static SingletonLazy getInstance() {if (instance == null){synchronized (SingletonLazy.class) {if(instance == null) {instance = new SingletonLazy();}}}return instance;}
- 我们便可以在 加锁操作 的外层再加上个 if 判断,判断 instance 对象是否已经被创建出来了
- 从而该代码只会在任意线程第一次调用 getInstance 方法时,才会进行加锁操作
- 从而此处不再是无脑加锁,而是满足了特定条件之后,才真正加锁
- 我们需要理解此处为什么会有两个相同的 if 判断
- 首先如果这两个 if 判断之间没有加锁操作,那么写两个一模一样 if 判断是毫无意义的
- 但是正因为这两个 if 判断之间有加锁操作,而加锁操作就可能会引起线程阻塞,当线程竞争到锁之后,再执行到第二个 if 判断的时候,可能与第一次执行 if 判断之前隔了很长一段时间
举例理解:
- 线程A 第一次调用 getInstance 方法,读取到 instance 为 null,通过第一次 if 判定,并成功为锁对象进行加锁操作,然后再次读取到 instance 为 null,通过第二次 if 判定,进而直接 new 出一个 instance 对象,最后再将锁释放
- 可能线程B 比线程A 晚一点点的调用了 getInstance 方法,可能此时线程A 并未修改完instance 的值,从而线程B 读取到 instance 为 null,通过了第一次 if 判定,然后进行阻塞等待线程A 释放锁,但正是在线程B 等待锁的在这段时间里,线程A 已经将 instance 对象给创建出来了,此时线程B 再获取到锁时,instance 的值已经发生改变了,线程B 再次读取 instance 的值,此时 instance 不为 null,从而未通过第二次 if 判断,直接返回 instance 的值,这就意味着第二次 if 判断成功阻止了线程B 再创建一个新的 instance 对象
- 根据上述例子,深入理解 图中第一个 if 负责判定是否要加锁,解决了每次调用getInstance 方法时都需要引入无意义的加锁操作,很大程度上减少了开销,第二个 if 负责判定是否要创建对象,是最初为了保障单例模式,引入的必要条件
- 这两 if 判断的目的是完全不相同的,只是碰巧代码是一样的!
- 上述仅解决了多线程之间 脏读 的问题,但是还可能会有 内存可见性问题
- 假设有很多线程,都去执行 getInstance 方法,这个时候便可能存在被优化的风险,即只有第一次读才是真正读了内存,后续都是读寄存器或 cache
- 同时还可能涉及到 指令重排序问题
- 编译器为了提高程序的效率,调整代码执行顺序
- 即 我们可以将 instance = new Singleton(),拆分为三个步骤
- 步骤 1:申请内存空间
- 步骤 2:调用构造方法,把这个内存空间初始化成一个合理的对象
- 步骤 3:把内存空间的地址赋值给 instance 引用
- 编译器可能将步骤的执行顺序由 1、2、3,优化重排序为 1、3、2
- 如果仅是在单线程场景下,执行步骤的调换是没有任何影响的
- 但是如果是在多线程环境下,我们举一个简单例子来理解指令重排序所带来的问题
举例理解:
- 假设编译器优化指令重排序,线程A 的步骤执行顺序变为 1、3、2,如果线程A 执行完步骤 1、3,正当要执行步骤 2 时,被切出 CPU,CPU 调度执行线程B
- 我们要注意到的是,此时线程A 执行完步骤 1、3 后会创建出一个非法对象,即该对象仅分配了内存,其数据是无效的,只有执行完步骤 2 才会把这个内存空间初始化成一个合理的对象
- 那么当 CPU 调度执行线程B 时,线程B 又正好调用 getInstance 方法,此刻便会进入第一个 if 判断,获取 instance 对象的值,来判断是否为 null
- 因为 instance 对象 已经被分配好了内存空间,所以线程B 获取到的 instance 对象值并不会为 null
- 所以线程B 将会直接返回该 instance 对象
- 注意此处线程B 返回的 instance 对象 是上述讲的非法对象,即仅分配了内存,其数据是无效的
- 所以之后 线程B 拿着这个非法对象,来进行使用便将会出现许多问题和错误
解决方法:
- 引入 volatile 关键字
- volatile 关键字的功能正好能解决 内存可见性 和 指令重排序
class SingletonLazy {private volatile static SingletonLazy instance = null;public static SingletonLazy getInstance() {if (instance == null){synchronized (SingletonLazy.class) {if(instance == null) {instance = new SingletonLazy();}}}return instance;}private SingletonLazy(){} }public class ThreadDemo20 {public static void main(String[] args) {SingletonLazy s1 = SingletonLazy.getInstance();SingletonLazy s2 = SingletonLazy.getInstance();System.out.println(s1 == s2);} }
- 以上完整的代码便是 线程安全的懒汉模式 完全体
相关文章:

2023.10.19 关于设计模式 —— 单例模式
目录 引言 单例模式 饿汉模式 懒汉模式 懒汉模式线程安全问题 分析原因 引言 设计模式为编写代码的 约定 和 规范 阅读下面文章前建议点击下方链接明白 对象 和 类对象 对象和类对象 单例模式 单个实例(对象)在某些场景中有特定的类,…...
4个鲜为人知的Python迭代过滤函数
在Python中,迭代器可以帮助你编写更多Pythonic的代码,并在处理长序列时提高效率,内置的itertools模块提供了几个有用的函数来创建迭代器。 当你只需要遍历迭代器、检索序列中的元素并对其进行处理,而无需将它们存储在内存中时&am…...

使用logger.error(“自定义错误信息描述“,e)将错误信息输出到日志文件上
之前一直用e.getMessage()来获取错误信息 import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;RestController public class ClassF…...

音乐的数字未来:虚拟演唱会与TikTok的巅峰融合
在数字时代,音乐产业正在经历着革命性的变革。虚拟演唱会与TikTok的融合正引领着音乐的数字未来,为艺术家、粉丝和创作者带来了前所未有的互动性和娱乐体验。本文将深入探讨这一巅峰融合,以揭示音乐产业的新前景。 虚拟演唱会的崛起 虚拟演唱…...

基于图像识别的跌倒检测算法 计算机竞赛
前言 🔥 优质竞赛项目系列,今天要分享的是 基于图像识别的跌倒检测算法 该项目较为新颖,适合作为竞赛课题方向,学长非常推荐! 🧿 更多资料, 项目分享: https://gitee.com/dancheng-senior/…...

NSS [SWPUCTF 2021 新生赛]PseudoProtocols
NSS [SWPUCTF 2021 新生赛]PseudoProtocols 先看题目,题目要求我们先找到hint.php。 看这个get请求头,我们先用php://filter协议读一波 得到提示,让我们前往/test2222222222222.php 源码如下 <?php ini_set("max_execution_time&qu…...

字节码进阶之JVM Attach API详解
字节码进阶之JVM Attach API详解 文章目录 字节码进阶之JVM Attach API详解附加到虚拟机加载代理和获取信息分离虚拟机 使用Attach API的基本步骤1. **获取虚拟机实例**:2. **附加到虚拟机**:3. **加载代理或获取信息**4. **从虚拟机分离**:…...

Kubernetes 部署 kubeflow1.6.1
前言 安装前请注意捋清楚版本关系,如kubeflow版本对应的K8S版本及其相关工具版本等等 我们此处使用的是是kubeflow-1.6.1和K8s-v1.22.8 单机部署 部署K8S 初始化Linux 1.关闭selinux setenforce 0 && sed -i "s/SELINUXenforcing/SELINUXdisable…...

设计模式:建造者模式(C#、JAVA、JavaScript、C++、Python、Go、PHP)
上一篇《策略模式》 下一篇《适配器模式》 简介: 建造者模式,它是一种对象构建模式,它提供了一种构建对象的最佳方式。这种模式适用于当对象的构建过程需要涉及到多个部分ÿ…...
Maxon Cinema 4D 2024:打造独一无二的视觉效果 模拟模块大更新
在视觉效果和3D建模领域,Maxon的Cinema 4D一直以其卓越的性能和创新的功能引领着时代潮流。今天,我们很高兴地宣布推出最新版本——Maxon Cinema 4D 2024(C4D 2024),它将再次提升行业标准,为设计师提供更强…...

16.2 ARP 主机探测技术
ARP (Address Resolution Protocol,地址解析协议),是一种用于将 IP 地址转换为物理地址(MAC地址)的协议。它在 TCP/IP 协议栈中处于链路层,为了在局域网中能够正确传输数据包而设计,…...

三级等保-linux服务器三权分立设置
安全问题 安全控制点 风险分析 风险等级 标准要求 加固建议 服务器未严格按照系统管理员权限、审计管理员权限、安全管理员权限进行分配管理员账户,未实现管理员用户的最小权限划分。 访问控制 可能存在管理员越权操作的风险 中 d)应授予管理用户所需的最…...

抓取网页的含义和URL基本构成
抓取网页是指通过爬虫程序从互联网上获取网页的内容和数据。抓取网页是爬虫的核心功能之一,通过抓取网页,可以获取到网页中的文本、图片、链接等信息,用于后续的数据分析、挖掘和应用。 URL(Uniform Resource Locator)…...
计算机毕业设计 机器学习深度学习人工智能
视频参考: 计算机毕业设计项目分享_哔哩哔哩_bilibili 基于深度学习的农业病虫害识别基于SpringBootVue的博客系统基于SpringBootVue的仓库管理系统基于卷积网络的花卉图像识别 毕业设计选题: VX:whbwqq123 基于机器学习的大气数据的污染物pm2.5预测基…...

施密特正交化
相信大家在平时的期末考试中一定少不了对某某向量组执行标准正交化类型的题目。今天我们从这个题目入手,说明这个如何执行施密特正交化,以及为什么要进行正交化。 一、例子 例子:设 a 1 [ 1 2 − 1 ] a_1\begin{bmatrix}1\\2\\-1\end{bmat…...
低代码开发:加速应用开发的利器
目录 一、引言 二、低代码开发的定义和原理 三、低代码开发的关键特性和优势 四、低代码开发的应用场景 五、低代码开发平台的市场现状和发展趋势 六、成功案例分析 七、结论 一、引言 随着信息技术的快速发展,企业对于应用开发的需求也日益增长。传统的应用…...

数据安全发展趋势与密码保护技术研究
随着数据跃升为新型生产要素,数据安全的内涵也从数据本身安全、数据资源安全,发展到数据资产安全三个层面提出了不同的要求,本文就是详细探讨数据安全的这三个层面的安全内容进行分析。 通过对数据安全不同发展阶段的安全需求和保障对象进行研…...
368周赛leetcode
1 2题元素和最小的山形三元组 经典动规 题目内容 给你一个下标从 0 开始的整数数组 nums 。 如果下标三元组 (i, j, k) 满足下述全部条件,则认为它是一个 山形三元组 : i < j < k nums[i] < nums[j] 且 nums[k] < nums[j] 请你找出 num…...
Vue 的 nextTick:深入理解异步更新机制
目录 一、前言 二、Vue.js 异步更新机制简述 三、Vue.nextTick原理 四、nextTick 的应用场景 1. 获取更新后的 DOM 元素 2. 在 DOM 更新后执行自定义的回调函数 3. 解决事件监听器中的更新问题 五、Vue.nextTick与其他异步更新方法的比较 六、总结 一、前言 Vue.js&a…...

SQL关于日期的计算合集
前言 在SQL Server中,时间和日期是常见的数据类型,也是数据处理中重要的一部分。SQL Server提供了许多内置函数,用于处理时间和日期数据类型。这些函数可以帮助我们执行各种常见的任务,例如从日期中提取特定的部分,计…...

网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...