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

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 先看题目&#xff0c;题目要求我们先找到hint.php。 看这个get请求头&#xff0c;我们先用php://filter协议读一波 得到提示&#xff0c;让我们前往/test2222222222222.php 源码如下 <?php ini_set("max_execution_time&qu…...

字节码进阶之JVM Attach API详解

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

Kubernetes 部署 kubeflow1.6.1

前言 安装前请注意捋清楚版本关系&#xff0c;如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)

上一篇《策略模式》 下一篇《适配器模式》 简介&#xff1a; 建造者模式&#xff0c;它是一种对象构建模式&#xff0c;它提供了一种构建对象的最佳方式。这种模式适用于当对象的构建过程需要涉及到多个部分&#xff…...

Maxon Cinema 4D 2024:打造独一无二的视觉效果 模拟模块大更新

在视觉效果和3D建模领域&#xff0c;Maxon的Cinema 4D一直以其卓越的性能和创新的功能引领着时代潮流。今天&#xff0c;我们很高兴地宣布推出最新版本——Maxon Cinema 4D 2024&#xff08;C4D 2024&#xff09;&#xff0c;它将再次提升行业标准&#xff0c;为设计师提供更强…...

16.2 ARP 主机探测技术

ARP &#xff08;Address Resolution Protocol&#xff0c;地址解析协议&#xff09;&#xff0c;是一种用于将 IP 地址转换为物理地址&#xff08;MAC地址&#xff09;的协议。它在 TCP/IP 协议栈中处于链路层&#xff0c;为了在局域网中能够正确传输数据包而设计&#xff0c;…...

三级等保-linux服务器三权分立设置

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

抓取网页的含义和URL基本构成

抓取网页是指通过爬虫程序从互联网上获取网页的内容和数据。抓取网页是爬虫的核心功能之一&#xff0c;通过抓取网页&#xff0c;可以获取到网页中的文本、图片、链接等信息&#xff0c;用于后续的数据分析、挖掘和应用。 URL&#xff08;Uniform Resource Locator&#xff09…...

计算机毕业设计 机器学习深度学习人工智能

视频参考&#xff1a; 计算机毕业设计项目分享_哔哩哔哩_bilibili 基于深度学习的农业病虫害识别基于SpringBootVue的博客系统基于SpringBootVue的仓库管理系统基于卷积网络的花卉图像识别 毕业设计选题&#xff1a; VX:whbwqq123 基于机器学习的大气数据的污染物pm2.5预测基…...

施密特正交化

相信大家在平时的期末考试中一定少不了对某某向量组执行标准正交化类型的题目。今天我们从这个题目入手&#xff0c;说明这个如何执行施密特正交化&#xff0c;以及为什么要进行正交化。 一、例子 例子&#xff1a;设 a 1 [ 1 2 − 1 ] a_1\begin{bmatrix}1\\2\\-1\end{bmat…...

低代码开发:加速应用开发的利器

目录 一、引言 二、低代码开发的定义和原理 三、低代码开发的关键特性和优势 四、低代码开发的应用场景 五、低代码开发平台的市场现状和发展趋势 六、成功案例分析 七、结论 一、引言 随着信息技术的快速发展&#xff0c;企业对于应用开发的需求也日益增长。传统的应用…...

数据安全发展趋势与密码保护技术研究

随着数据跃升为新型生产要素&#xff0c;数据安全的内涵也从数据本身安全、数据资源安全&#xff0c;发展到数据资产安全三个层面提出了不同的要求&#xff0c;本文就是详细探讨数据安全的这三个层面的安全内容进行分析。 通过对数据安全不同发展阶段的安全需求和保障对象进行研…...

368周赛leetcode

1 2题元素和最小的山形三元组 经典动规 题目内容 给你一个下标从 0 开始的整数数组 nums 。 如果下标三元组 (i, j, k) 满足下述全部条件&#xff0c;则认为它是一个 山形三元组 &#xff1a; 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中&#xff0c;时间和日期是常见的数据类型&#xff0c;也是数据处理中重要的一部分。SQL Server提供了许多内置函数&#xff0c;用于处理时间和日期数据类型。这些函数可以帮助我们执行各种常见的任务&#xff0c;例如从日期中提取特定的部分&#xff0c;计…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器

——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的​​一体化测试平台​​&#xff0c;覆盖应用全生命周期测试需求&#xff0c;主要提供五大核心能力&#xff1a; ​​测试类型​​​​检测目标​​​​关键指标​​功能体验基…...

Go 语言接口详解

Go 语言接口详解 核心概念 接口定义 在 Go 语言中&#xff0c;接口是一种抽象类型&#xff0c;它定义了一组方法的集合&#xff1a; // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的&#xff1a; // 矩形结构体…...

【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制

目录 节点的功能承载层&#xff08;GATT/Adv&#xff09;局限性&#xff1a; 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能&#xff0c;如 Configuration …...

ubuntu系统文件误删(/lib/x86_64-linux-gnu/libc.so.6)修复方案 [成功解决]

报错信息&#xff1a;libc.so.6: cannot open shared object file: No such file or directory&#xff1a; #ls, ln, sudo...命令都不能用 error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory重启后报错信息&…...

React核心概念:State是什么?如何用useState管理组件自己的数据?

系列回顾&#xff1a; 在上一篇《React入门第一步》中&#xff0c;我们已经成功创建并运行了第一个React项目。我们学会了用Vite初始化项目&#xff0c;并修改了App.jsx组件&#xff0c;让页面显示出我们想要的文字。但是&#xff0c;那个页面是“死”的&#xff0c;它只是静态…...

写一个shell脚本,把局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里

写一个shell脚本&#xff0c;把局域网内&#xff0c;把能ping通的IP和不能ping通的IP分类&#xff0c;并保存到两个文本文件里 脚本1 #!/bin/bash #定义变量 ip10.1.1 #循环去ping主机的IP for ((i1;i<10;i)) doping -c1 $ip.$i &>/dev/null[ $? -eq 0 ] &&am…...

如何把工业通信协议转换成http websocket

1.现状 工业通信协议多数工作在边缘设备上&#xff0c;比如&#xff1a;PLC、IOT盒子等。上层业务系统需要根据不同的工业协议做对应开发&#xff0c;当设备上用的是modbus从站时&#xff0c;采集设备数据需要开发modbus主站&#xff1b;当设备上用的是西门子PN协议时&#xf…...

aurora与pcie的数据高速传输

设备&#xff1a;zynq7100&#xff1b; 开发环境&#xff1a;window&#xff1b; vivado版本&#xff1a;2021.1&#xff1b; 引言 之前在前面两章已经介绍了aurora读写DDR,xdma读写ddr实验。这次我们做一个大工程&#xff0c;pc通过pcie传输给fpga&#xff0c;fpga再通过aur…...

机器学习复习3--模型评估

误差与过拟合 我们将学习器对样本的实际预测结果与样本的真实值之间的差异称为&#xff1a;误差&#xff08;error&#xff09;。 误差定义&#xff1a; ①在训练集上的误差称为训练误差&#xff08;training error&#xff09;或经验误差&#xff08;empirical error&#x…...