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);}
}
  • 以上完整的代码便是 线程安全的懒汉模式 完全体

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/167773.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

XPS就是分一下峰没你想的那么简单!-科学指南针

还记得前一段时间的一篇刷屏的经典文章吗! 林雪平大学(Linkping University)的Grzegorz Greczynski和Lars Hultman二人发表观点性文章,对诺奖得主K. Siegbahn推荐的XPS校准方法可能存在的问题进行了阐述与批评,并提出建议。文章原标题为“Compromising S…

程序员的金饭碗在哪里?这几个网站建议收藏!帮助你一步登天

俗话说的好,一个趁手的工具抵过诸葛亮。尤其是在程序员这个领域,不仅是一个非常和科技挂钩的领域,而且更新速度非常的迅速。 连java python都在更新,手头上写码的工具却还是老三样怎可行?这就需要我们跟上时代的脚步&…

统信操作系统UOS上安装arm64版nginx

原文链接:统信操作系统UOS上安装arm64版nginx hello,大家好啊,今天给大家带来一篇在统信桌面操作系统UOS上安装arm64版nginx的文章,本篇文章主要是给大家提供一种下载离线nginx软件包的方法,拿到软件包可以去不能链接互…

众和策略:华为汽车概念活跃,圣龙股份斩获12板,华峰超纤涨10%

华为轿车概念23日盘中再度生动,到发稿,华峰超纤涨超10%,佛山照明、圣龙股份、隆基机械、银宝山新等涨停,赛力斯涨近6%。 值得注意的是,圣龙股份已接连12个交易日涨停。 昨日晚间,圣龙股份宣布前三季度成果…

contenteditable实现文本内容确认提示

功能需求: 列表进行批量查询,需要对输入的值做提交校验,分三种情况: 若部分字符串有误,部分字符串需要变更字体颜色做提示,再次点击确认则对部分正确数据执行批量查询 若全部数据有误则变更字体颜色做提示&…

win7录屏软件哪个好用?盘点3款实用软件

在当今科技迅猛发展的时代,录屏已经成为了教育、演示和内容创作的重要工具。对于使用windows 7操作系统的用户来说,选择合适的录屏软件至关重要。可是win7录屏软件哪个好用呢?在本文中,我们将介绍3款常用的win7录屏软件。通过比较…

鸿蒙状态栏设置

鸿蒙状态栏设置 基于鸿蒙 ArkTS API9,设置状态栏颜色,隐藏显示状态栏。 API参考文档 参考文档 新建项目打开之后发现状态栏是黑色的,页面颜色设置完了也不能影响状态栏颜色,如果是浅色背景,上边有个黑色的头&#…

忆联SR-IOV解决方案:助力云数据中心节能提效,向“绿”而行

随着AI时代的到来,云数据中心如何实现节能提效正成为热门话题。其中,SR-IOV技术凭借灵活度高以及可节约虚拟化业务算力等优势,是打造绿色低碳云数据中心的重要解决方案之一。 一、什么是SR-IOV 技术 SR-IOV 是由国际组织 PCI-SIG 组织定义的…

65%更小的APK和70%更少的内存:如何优化我的Android App的内存

65%更小的APK和70%更少的内存:如何优化我的Android App的内存 (Note: This is a translation of the provided title) 为什么应用程序内存很重要? 使用最少的内存的高效应用程序可以提升性能,节省设备资源并延长电池寿命。它们提供流畅的用…

同为科技(TOWE)机架PDU产品在IDC数据中心机房建设中的应用

当今社会互联网发展迅速, 随着带宽需求的提升, 网络的保密性、安全性的要求就越来越迫切。PDU(Power Distribution Unit) 是 PDU具备电源分配和管理功能的电源分配管理器。PDU电源插座是多有设备运行的第一道也是最为密切的部件, PDU的好坏直…

html内连框架

src:引用页面地址 name&#xff1a;框架标识名称 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body> <!--iframe src&#xff1a;地址 w-h&#xff…

电商行业常见信息化工具及电商API接口一体化解决方案

主流的电商行业随着市场趋势发展&#xff0c;企业管理需求也日渐增多&#xff0c;不同的业务管理又有不同的系统支撑&#xff0c;业务增长的同时&#xff0c;数据的交互、管理的难点也在频频而出&#xff0c;那么电商企业如何实现信息一体化&#xff1f;如何解决目前存在的多系…

JVM 基础篇:类加载器

一.了解JVM 1.1什么是JVM JVM是Java Virtual Machine&#xff08;Java虚拟机&#xff09;的缩写&#xff0c;是一个虚构出来的计算机&#xff0c;是通过在实际的计算机上仿真模拟计算机功能来实现的&#xff0c;JVM屏蔽了与具体操作系统平台相关的信息&#xff0c;Java程序只需…

【React】高频面试题

1. 简述下 React 的事件代理机制&#xff1f; React使用了一种称为“事件代理”&#xff08;Event Delegation&#xff09;的机制来处理事件。事件代理是指将事件处理程序绑定到组件的父级元素上&#xff0c;然后在需要处理事件的子元素上触发事件时&#xff0c;事件将被委托给…

Python —— hou.NetworkItem class

在一个network内&#xff0c;所有可见元素的基类&#xff1b; 此类没有方法&#xff0c;仅作为 hou.NetworkMovabelItem、hou.NodeConnection 基类存在&#xff0c;这两个子类在网络编辑器内均是可见的&#xff0c;是没有真正有意义的基类的&#xff1b;通过提供一个公共的基类…

deque的简单了解

介绍 deque是一种结合了list和vector两者优势的一种容器&#xff0c;它既可以支持下标的随机访问&#xff0c;并且头插头删的效率都不低&#xff0c;但相对也存在一定的缺陷&#xff0c;在中间插入和遍历上&#xff0c;消耗相对较大。 deque并不是真正连续的空间&#xff0c;…

读卡器+芯片

RFID Reader 产品参数 产品型号 RFID Reader 尺寸(mm) 104*70*11.7mm 外观颜色 黑 材质 ABS 接口 MINI 功率 0.1W 电源 5V/1A 读卡距离 40mm 工作温度 -40~85℃ 工作频率 13.56Mhz 串口功能 支持 支持协议 ISO/IEC 14443、ISO/IEC 15693 产品参数 …

分布式存储 vs. 全闪集中式存储:金融数据仓库场景下的性能对比

作者&#xff1a;深耕行业的 SmartX 金融团队 张德敏 近年来随着金融行业的高速发展&#xff0c;经营决策者及监管机构对信息时效性的要求越来越高&#xff0c;科技部门面临诸多挑战。例如&#xff0c;不少金融机构使用数仓业务系统&#xff0c;为公司高层提供日常经营报表&am…

【剑指Offer】36.二叉搜索树与双向链表

题目 输入一棵二叉搜索树&#xff0c;将该二叉搜索树转换成一个排序的双向链表。如下图所示 数据范围&#xff1a;输入二叉树的节点数 0≤n≤1000&#xff0c;二叉树中每个节点的值 0≤val≤1000 要求&#xff1a;O(1)&#xff08;即在原树上操作&#xff09;&#xff0c;时间…

YOLOv5论文作图教程(1)— 软件介绍及下载安装(包括软件包+下载安装详细步骤)

前言:Hello大家好,我是小哥谈。在学习YOLOv5算法的过程中,很多同学都有发表论文的需求。作为文章内容的支撑,图表是最直接的整合数据的工具,能够更清晰地反映出研究对象的结果、流程或趋势。在发表论文的时候,审稿人除了关注论文的内容和排版外,也会审核图表是否清晰美观…