单例模式学习

单例模式是应用最广的设计模式之一,也是程序员最熟悉的一个设计模式,使用单例模式的类必须保证只能有创建一个对象。
一、为什么要使用单例?
在开发过程中,很多时候一个类我们希望它只创建一个对象,比如:线程池、缓存、网络请求等。当这类对象有多个实例时,程序就可能会出现异常,比如:程序出现异常行为、得到的结果不一致等。

这时候就应该使用单例模式。

单例主要有这两个优点:

1、提供了对唯一实例的受控访问。

2、由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。

二、实现单例的 5 种方式
实现单例模式主要有以下几个关键点:

构造函数设置为 private ,这避免外部通过 new 创建实例;
通过一个静态方法或者枚举返回单例类对象;
考虑对象创建时的线程安全问题,确保单例类的对象有且仅有一个,尤其是在多线程环境下;
确保单例类对象在反序列化时不会重新构建对象。
考虑是否支持延迟加载;
下面是常见的集中单例模式的实现方式
1、饿汉式
在类加载的期间,就已经将 instance 静态实例初始化好了,所以,instance 实例的创建是线程安全的。不过,这样的实现方式不支持延迟加载实例。

public class Singleton {private Singleton(){}private static final Singleton instance = new Singleton();public static Singleton getInstance(){return instance;}
}

2、懒汉式
懒汉式相对于饿汉式的优势是支持延迟加载。

public class Singleton {private Singleton(){}private static Singleton instance;public static synchronized Singleton getInstance(){if (instance == null) {instance = new Singleton();}return instance;}
}

但它的缺点也很明显,getInstance 使用了 synchronize 实现线程同步,导致这个方法的并发很低,每次调用都会频繁的枷锁、释放锁,会导致性能瓶颈。

3、双重检测
饿汉式不能延时加载,懒汉式有性能问题,而双重检测方式既支持延迟加载、又支持高并发的单例实现方式。

当 instance 对象被创建后,再次调用 getInstance 方法不再会进入 synchronize 加锁的代码之中。

它的优点是:资源利用率高,第一次执行 getInstance 时才会被实例化,效率高。缺点是:第一次加载反应稍慢。

public class Singleton {private Singleton(){}private static Singleton instance;public static Singleton getInstance(){if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}

有时候,面试官会问这种实现方式有什么问题。他们指的就是指令重排序。

instance = new Singleton(); 并不是一个原子操作, 这句代码实际执行了三件事。

1、 给 Singleton 的实例分配内存;

2、调用 Singleton 的构造函数,初始化成员变量;

3、将 instance 的对象指向分配的内存空间。

因为 Java 编译器允许处理器乱序执行,2、3的顺序是无法保证的。如果是 1-3-2 执行的顺序,当执行完 3 、2未执行之前,被切换到 B 线程,此时 instance 已经非空,B 会直接取走 instance,在使用时就会出错。

这就是指令重排。
解决办法也很简单:只需要给 instance 成员变量加上 volatile 关键字,就可以禁止指令重排序。

其实这个问题在高版本的 java 中已经被解决了,解决方式也很简单,就是把对象 new 操作和初始化操作设计为原子操作,就自然能禁止重排序。

4、静态内部类
除了以上方法外,使用 Java 的静态内部类也能够实现。

public class Singleton {private Singleton(){}private static class Instance {private static final Singleton instance = new Singleton();} public static Singleton getInstance(){return Instance.instance;}
}

当第一次加载 Singleton 类时并不会初始化 instance,只有在第一次调用 Singleton 的 getInstance 方法时才会导致 instance 被初始化。

第一次调用 getInstance 方法时会导致虚拟机加载 Instance 类,这种方式不仅能保证线程安全,也能够保证单例对象唯一,同时也延迟了单例的实例化。

5、枚举
枚举是单例最简单的实现方式,这种实现方式通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。

public enum Singleton {INSTANCE;
}

三、单例存在哪些问题?
1、对 OOP 特性的支持不友好
面向对象的四大特征是:封装、继承、多态。单例对继承、多态特性的支持不友好。

虽然从理论上来讲,单例类也可以被继承、也可以实现多态,但实现起来会非常奇怪。所以,一旦将某个类设计成到单例类,也就意味着放弃了继承和多态这两个面向对象的特性,也就相当于损失了可以应对未来需求变化的扩展性。

2、单例对代码的扩展性不友好
我们知道,单例类只能有一个对象实例。但如果未来改需求了,需要创建两个或多个实例,就需要对代码有比较大的改动。

3、单例不支持有参数的构造函数
单例不支持有参数的构造函数,如果想要传递参数,只能在 getInstance 方法中添加参数,或者定义方法传递参数。

4、针对这些问题,有何替代的解决方案?
为了保证全局唯一,除了使用单例,还可以用静态方法来实现。不过,静态方法这种实现思路,并不能解决之前提到的问题。

实际上,它比单例更加不灵活,比如,它无法支持延迟加载。

目前并没有什么很好的方式来解决。

四、单例对象的作用域的范围
单例模式的类只能创建一个对象,这个对象的作用域是整个 APP 的生命周期,也就是进程中唯一。

当我们打开 APP 后,系统会开启一个进程,并分配给 APP,接着进程会一条一条地执行 APP 文件中包含的代码,比如 当读到 User user = new User(); 这条语句的时候,它就在自己的地址空间中创建一个 user 临时变量和一个 User 对象。

进程之间是不共享地址空间的,如果我们的 APP 开启多个进程,那么每个进程都会分配新的地址空间,单例模式就会失效。。

单例类中对象的唯一性的作用范围是进程内的,在进程间是不唯一的。

五、单例模式是如何保证唯一性的
这里就需要了解JVM 的类加载机制。

虚拟机的类加载是采用双亲委派模型。

它的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会去加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(他的搜索范围中,没有找到这个类),子加载器才会去尝试加载。

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

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

相关文章

springboot(ssm智慧校园之家长子系统 智慧校园系统Java系统

springboot(ssm0智慧校园之家长子系统 智慧校园系统Java系统 开发语言:Java 框架:ssm/springboot vue JDK版本:JDK1.8(或11) 服务器:tomcat 数据库:mysql 5.7(或8.0&#xff09…

苹果发布了一个Ferret(雪貂)多模态大模型,在一个无人问津的角落被一位博主捞起来

苹果12月14日释放了一个名为Ferret的多模态大语言模型,有的翻译是雪貂,有的是法学硕士,要我说,还是叫雪貂吧,接地气亲民,将来犯蠢的时候出来发张雪貂的可爱表情包作公关就完事了,你个法学硕士到…

C语言—每日选择题—Day63

指针相关博客 打响指针的第一枪:指针家族-CSDN博客 深入理解:指针变量的解引用 与 加法运算-CSDN博客 第一题 1. 设C语言中,一个int型数据在内存中占2个字节,则unsigned int型数据的取值范围为 A:0~255 B:0…

mysql8.x版本_select语句源码跟踪

总结 源码基于8.0.34版本分析,函数执行流程含义大致如下: do_command 方法从连接中读取命令并执行,调用 dispatch_command 对命令进行分发。dispatch_command 调用 mysql_parse 对命令进行解析,如果遇到一条语句用 ; 分隔多条命…

CGAL中三角形曲面网格近似

1、介绍 此软件包实现了变分形状近似(VSA)方法,通过更简单的表面三角形网格来近似输入表面网格。该算法的输入必须是: 三角形分割;组合2流形 输出是一个三角形汤,可以构建成多边形曲面网格。 给定一个输入曲…

linux操作系统——进程(二) 进程状态

进程状态 你真正的理解了进程的状态嘛?特别是操作系统教材中学过的进程状态,你真的理解了吗? 教材上关于进程状态的说明 下面我们以下图为例: 这是教材上对操作系统的说明,但是它并没有详细的说明,这些状态具体是什么&#xf…

大语言模型说明书

在浩瀚的信息宇宙中,大语言模型如同一颗璀璨的星星正在熠熠生辉。21世纪以来,人工智能可谓是飞速发展,从简单的神经网络到大语言模型、生成式AI,这并非仅仅是一种技术的进步,更是人类智慧的飞跃。大语言模型不仅仅是语…

获取请求的真实ip

一、node代码 const app express(); app.get("/abc", (req, res) > {const forwardedForHeader req.get("X-Forwarded-For");const realIpHeader req.get("X-Real-IP");let realIp null;if (forwardedForHeader && forwardedFor…

用makeself.sh脚本来制作一键发布文件(.run)

----------------------------------------------------------- author: hjjdebug date : 2023年 12月 25日 星期一 15:59:06 CST description: 用makeself.sh脚本来制作一键发布文件(.run) ---------------------------------------------------------- 我看到nvidia 发布驱动…

使用easyexcel对导出表格添加合计行

文章目录 一、背景二、实现1、写法一2、写法二 三、遇到的问题四、参考 一、背景 近期开发的一个新功能需要导出和前端展示样式一致的统计表格,而前端使用的elementui的table组件,show-summary属性选择后可以自动计算。后端导出时其他单元格与返回前端展…

华为数通方向HCIP-DataCom H12-831题库(多选题:241-249)

第241题 (NEW) 以下哪些操作可能会影响客户网络的正常运行? A、从设备上下载日志 B、软件升级 C、路由协议配置变更 D、debug核心交换机上转发的所有IP报文 答案:ABCD 解析: 第242题 对于防火墙的默认安全区 Trust 和 Untrust 的说法,正确的有 A、从 Trust 区域访问 Untr…

Vue在页面上添加水印

第一步:在自己的项目里创建一个js文件;如图所示我在在watermark文件中创建了一个名为waterMark.js文件。 waterMark.js /** 水印添加方法 */ let setWatermark (str1, str2) > {let id 1.23452384164.123412415if (document.getElementById(id) …

【MYSQL】MYSQL 的学习教程(六)之 SQL 语句执行流程

1. 一条 SQL 查询语句是如何被执行的 MySQL 的基本架构示意图如下所示: MYSQL 线程处理请求流程: SQL 接口:MySQL 中处理请求的线程在获取到请求以后获取 SQL 语句去交给 SQL 接口去处理查询解析器:解析器会将 SQL 接口传递过来…

mysql高级查询

当涉及到"高级查询"时,通常指的是使用SQL语言进行复杂的数据查询,包括多表连接、聚合函数、子查询、联合查询和条件筛选等。 多表连接查询: SELECT customers.customer_name, orders.order_number FROM customers JOIN orders ON…

操作系统期末复习知识点二计算与应用

1.理解银行家算法判断死锁的定理并能计算相关的参数。 2.能利用LRU、FIFO算法求缺页率。 3.纯页式管理中,求逻辑地址对应的物理地址,页号、页内地址长度,画出逻辑地址的格式,在引入块表时,求出有效访问时间。 4.可变分…

【Java】SpringBoot快速整合Kafka

目录 1.什么是Kafka? 主要特点和概念: 主要组成部分: 2.Kafka可以用来做什么? 3.SpringBoot整合Kafka步骤: 1. 添加依赖: 2. 配置 Kafka: 3. 创建 Kafka 生产者: 4. 创建 Kafka 消费者: 5. 发布消息: 6. 使…

【MySQL学习笔记007】约束

1、概述 (1)概念:约束是作用于表中字段上的规则,用于限制存储在表中的数据。 (2)目的:保证数据库中数据的正确、有效性和完整性。 (3)分类 约束 描述 关键字 …

【各种**问题系列】Java 数组集合之间的相互转换

📌 问题点: 在 Coding 过程中经常会遇到数组、List、Set、Map 之间的相互转换......这里记录一下转换的几种方式。😶😶😶 目录 📌 集合转换 1.数组 转 List: 2.List 转 数组: 3…

如何将本地websocket发布至公网并实现远程访问服务端

文章目录 1. Java 服务端demo环境2. 在pom文件引入第三包封装的netty框架maven坐标3. 创建服务端,以接口模式调用,方便外部调用4. 启动服务,出现以下信息表示启动成功,暴露端口默认99995. 创建隧道映射内网端口6. 查看状态->在线隧道,复制所创建隧道的公网地址加端口号7. 以…

AG16KDDF256 User Manual

AGM AG16KDDF256 是由 AGM FPGA AG16K 与 DDR-SDRAM 叠封集成的芯片,具有 AG16K FPGA的可编程功能,提供更多可编程 IO,同时内部连接大容量 DDR-SDRAM。  FPGA 外部管脚 FBGA256 封装,管脚说明请见下表 Table-1: Tab…