JAVA设计模式——单例模式

单例模式是应用最广的设计模式之一,也是程序员最熟悉的一个设计模式,使用单例模式的类必须保证只能有创建一个对象。

今天主要是回顾一下单例模式,主要是想搞懂以下几个问题

为什么要使用单例?
如何实现一个单例?
单例存在哪些问题?
单例对象的作用域的范围
单例模式是如何保证唯一性的

一、为什么要使用单例?🍉

在开发过程中,很多时候一个类我们希望它只创建一个对象,比如:线程池、缓存、网络请求等。当这类对象有多个实例时,程序就可能会出现异常,比如:程序出现异常行为、得到的结果不一致等。

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

单例主要有这两个优点:

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/7284.shtml

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

相关文章

c++11/c++98动态规划入门第5课,经典DP问题 --- 区间

第1题 取数问题 查看测评数据信息 有一排N个数,你和小明2个人玩游戏,每个人轮流从2端取数,每次可以从左或右取,不能从中间取。你取的所有的数的和是你的得分,小明取的所有的数的和是小明的得分。如果你先取&#x…

ISP 模块原理

图像锐化: 图像信号处理芯片设计原理----13 图像锐化 - 知乎 (zhihu.com) bayer /RGB去噪: 图像信号处理芯片设计原理——12 RAW域和YUV域上的去噪 - 知乎 (zhihu.com) 图像去噪技术简要总结 - 知乎 (zhihu.com) (72条消息) 非局部均值去噪&#xf…

【图像分割】基于蜣螂优化算法DBO的Otsu(大津法)多阈值电表数字图像分割 电表数字识别【Matlab代码#51】

文章目录 【可更换其他算法,获取资源请见文章第5节:资源获取】1. 原始蜣螂优化算法1.1 滚球行为1.2 跳舞行为1.3 繁殖行为1.4 偷窃行为 2. 多阈值Otsu原理3. 部分代码展示4. 仿真结果展示5. 资源获取说明 【可更换其他算法,获取资源请见文章第…

springboot 项目启动不打印spring 启动日志

今天项目遇到一个很奇怪的问题,服务在启动时,不打印spring 的启动日志。经过排查发现是因为其他的依赖引入了 log4j 的依赖,因为我们的项目用的是logback,所以项目中没有log4j 的相关配置,所以干扰到了日志的打印 原因…

Vue入门项目——WebApi

Vue入门——WebApi vue3项目搭建组合式API响应式APIreactive()ref() 生命周期钩子computed计算属性函数watch监听函数父子通信模板引用组合选项 vue3项目搭建 简单看下Vue3的优势吧 下载安装npm及node.js16.0以上版本(确保安装成功可用如下代码检查版本&#xff0…

工厂电能质量治理解决方案

1、概述 谐波的危害十分严重,尤其在工厂这种设备较多的场合。大部分设备都是谐波源,谐波使电能的生产、传输和利用的效率降低,使电气设备过热、产生振动和噪声,并使绝缘老化,使用寿命缩短,甚至发生故障或烧…

RocketMQ 5.0 无状态实时性消费详解

作者:绍舒 背景 RocketMQ 5.0 版本引入了 Proxy 模块、无状态 pop 消费机制和 gRPC 协议等创新功能,同时还推出了一种全新的客户端类型:SimpleConsumer。 SimpleConsumer 客户端采用了无状态的 pop 机制,彻底解决了在客户端发布…

创作纪念日——Hello World

创作纪念日——Hello World 导读 正在报告&#xff01; ——迅捷斥候 成就 精通多种语言的Hello World 1. C #include int main() {printf("Hello, World");return(0); }2. C int main() {std::cout << "Hello World";return 0; }3. C# using…

QT字节数组类QByteArray

QT字节数组类QByteArray 初始化访问某个元素截取字符串获取字节数组的大小数据转换与处理Hex转换数值转换与输出 字母大小写转换字符串数值转化为各类数值QBQyteArray和char*互转QByteArray 和std::string互转与字符串QString互转QByteArray和自定义结构体之间的转化判断是否为…

区块链实验室(11) - PBFT耗时与流量特征

以前面仿真程序为例&#xff0c;分析PBFT的耗时与流量特征。实验如下&#xff0c;100个节点构成1个无标度网络&#xff0c;节点最小度为5&#xff0c;最大度为38. 从每个节点发起1次交易共识。统计每次交易的耗时以及流量。本文所述的流量见前述仿真程序的说明:区块链实验室(3)…

13.4.2 【Linux】sudo

相对于 su 需要了解新切换的使用者密码 &#xff08;常常是需要 root 的密码&#xff09;&#xff0c; sudo 的执行则仅需要自己的密码即可。sudo 可以让你以其他用户的身份执行指令 &#xff08;通常是使用 root 的身份来执行指令&#xff09;&#xff0c;因此并非所有人都能够…

“深入解析Spring Boot:原理、特性与最佳实践“

标题&#xff1a;深入解析Spring Boot&#xff1a;原理、特性与最佳实践 摘要&#xff1a;本文将深入解析Spring Boot的原理、特性和最佳实践&#xff0c;帮助开发工程师更好地理解和使用Spring Boot。文章包括Spring Boot的背景介绍、核心原理、常用特性和最佳实践&#xff0…

AcWing 1210. 连号区间数

输入样例1&#xff1a; 4 3 2 4 1输出样例1&#xff1a; 7输入样例2&#xff1a; 5 3 4 2 5 1输出样例2&#xff1a; 9样例解释 第一个用例中&#xff0c;有 77 个连号区间分别是&#xff1a;[1,1],[1,2],[1,3],[1,4],[2,2],[3,3],[4,4][1,1],[1,2],[1,3],[1,4],[2,2],[3,3…

【七天入门数据库】第五天 MySQL的备份恢复

系列文章传送门&#xff1a; 【七天入门数据库】第一天 MySQL的安装部署 【七天入门数据库】第二天 数据库理论基础 【七天入门数据库】第三天 MySQL的库表操作 【七天入门数据库】第四天 数据操作语言DML 一、日志管理 &#xff08;一&#xff09;日志类型 错误日志&am…

SAP ERP系统 MARA, MARC分别起到什么作用

“MARA"和"MARC” "MARA"和"MARC"是SAP系统中的两个重要的数据表&#xff0c;它们分别用于存储物料主数据和物料的工厂级数据。在SAP ERP系统中&#xff0c;物料主数据是核心数据之一&#xff0c;用于描述和管理公司生产和采购的物料。 MARA 表…

Linux系统知识1—Linux命令基础格式,什么是命令,命令行,ls命令入门,ls命令的参数和选项,-a,-l -h选项的使用及组合使用

一.什么是命令&#xff0c;命令行 &#xff0e;命令行&#xff1a;即 Linux 终端&#xff08; Terminal )&#xff0c;是一种命令提示符页面。以纯"字符"的形式操作系统&#xff0c;可以使用各种字符化命令对系统发出操作指令。 &#xff0e;命令&#xff1a;即 Lin…

redis(9):spring里面使用redis

1 创建一个mave项目 自行创建一个maven项目 2 修改pom.xml <properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.7</maven.compiler.source><maven.compiler.target>1.7</maven…

SpringMVC

文章目录 一.SpringMVC的概念1.1 Spring的概念1.2 MVC和SpringMVC的关系 二.SpringMVC步骤2.1 创建一个springMVC项目2.2 获取参数的功能2.3 输出参数的功能 三.SpringMVC的注解介绍3.1 获取参数3.1.1 获取参数单个参数3.1.2 获取多个参数3.1.3获取对象3.1.4 获取json对象3.1.5…

hqyj—驱动—day3

ioctl控制马达&#xff0c;LED灯&#xff0c;风扇&#xff0c;蜂鸣器运行 LED LED驱动程序&#xff1a; #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/io.h> #include "stm32mp1xx_gpio.h"…

深度学习初探

1.深度学习模型训练过程 1&#xff09;数据&#xff08;数据清洗标注&#xff09; 2&#xff09;模型&#xff08;模型的构建和初始化&#xff09; 3&#xff09;损失&#xff08;前向传播的过程&#xff0c;得到模型的输出和真实标签的差异值&#xff0c;称之为损失&#x…