【设计模式】单例模式、“多例模式”的实现以及对单例的一些思考

文章目录

  • 1.概述
  • 2.单例模式实现代码
    • 2.1.饿汉式单例
    • 2.2.懒汉式单例
    • 2.3.双检锁单例
    • 2.4.静态内部类单例
    • 2.5.枚举单例
  • 3.对单例的一些思考
    • 3.1.是否需要严格的禁止单例被破坏?
    • 3.2.懒汉式真的比饿汉式更佳吗?
    • 3.3.单例存在的问题
  • 4.其他作用范围的单例模式
    • 4.1.线程内的单例
    • 4.2.进程间的单例
  • 5.“多例模式”
  • 6.总结

1.概述

单例模式是设计模式中最简单的一种,对于很多人来说,单例模式也是其接触的第一种设计模式,当然,我也不例外。这种设计模式在学习、面试、工作的过程中广泛传播,相信不少人在面试时遇到过这样的问题:“说说你最熟悉的集中设计模式”,第一个脱口而出的就是单例模式。

所谓的单例模式,就是在一定的作用范围内保证只有一个实例,这种模式,简单,但是想要使用好它,还需要学习一下它延伸出来的其他知识点,本篇博文就对单例模式做一下简单的整理,主要会包含以下几部分内容:

  • 单例模式的代码如何编写?
  • 是否需要严格的禁止单例被破坏?
  • 饿汉式和懒汉式应该如何选择?
  • 单例模式存在什么问题?
  • 线程内单例和进程间单例如何实现?
  • 什么叫做“多例模式”?

2.单例模式实现代码

单例模式的实现代码很多,下面会例举一些常见的方式。

Java中,单例模式的作用范围一般情况下指的是当前的Java进程,也就是进程内的对象保证唯一(当然还有线程内、进程之间的单例,下面会提到),所以我们需要保证实例只会被初始化一次,如何保证呢?

2.1.饿汉式单例

一个简单的做法,就是私有化构造方法,也就是不让外部的客户端对象来调用new方法,创建新的实例,而是在项目启动时,由单例类自行初始化,这就是饿汉式单例

/*** 饿汉式单例*/
public class HungrySingleton {private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();private HungrySingleton() {}public static HungrySingleton getInstance() {return HUNGRY_SINGLETON;}
}

2.2.懒汉式单例

如果不想再项目启动时初始化,而是在使用的时候再初始化对象,可以将对象的创建放到getInstance方法中,这种方式叫做懒汉式单例。这种方式在多线程的情况下会有线程安全问题,需要在创建对象时加锁。

/*** 懒汉式单例*/
public class LazySingleton {private static volatile LazySingleton lazySingleton;private LazySingleton() {}public static synchronized LazySingleton getInstance() {if (lazySingleton == null) {lazySingleton = new LazySingleton();}return lazySingleton;}
}

2.3.双检锁单例

在方法加的是类锁,一次只有一个线程可以获取到单例对象,为了提高获取对象的效率,取消在方法上的类锁,转而只给创建对象的那一行代码加锁。但是在并发的情况下,多个线程同时进入getInstance方法,都可以通过lazySingleton == null的判断,并在加锁那一行排队,每个线程都会创建一个新的对象,所以,我们需要在锁里面再判断一次对象是否创建。
这种在加锁的代码前后都进行一次相同判断的做法,我们叫做双重检查锁,简称:双检锁

/*** 双检锁单例*/
public class LazySingleton {private static volatile LazySingleton lazySingleton;private LazySingleton() {}public static LazySingleton getInstance() {if (lazySingleton == null) {synchronized (LazySingleton.class) {if (lazySingleton == null) {lazySingleton = new LazySingleton();}}}return lazySingleton;}
}

2.4.静态内部类单例

上面的懒汉式是为了保证懒加载的同时,又不能有线程安全问题,我们采用了加锁的方式,那么不加锁行不行呢?
当然是可以的,我们采用静态内部类在受访问时才会初始化的特性,来实现懒加载。

/*** 静态内部类单例*/
public class StaticClassSingleton {private StaticClassSingleton() {}public static StaticClassSingleton getInstance() {return InnerStaticClassSingleton.STATIC_CLASS_SINGLETON;}private static class InnerStaticClassSingleton {private static final StaticClassSingleton STATIC_CLASS_SINGLETON = new StaticClassSingleton();}
}

2.5.枚举单例

Java中的枚举是天然的单例模式,这种方式实现最简单,而且不会有线程安全问题,也能避免通过反射或者反序列化创建新的对象。
下面代码中的INSTANCE就是单例对象了。

/*** 枚举单例*/
public enum EnumSingleton {INSTANCE;
}

3.对单例的一些思考

3.1.是否需要严格的禁止单例被破坏?

在上面的代码例子中,我们采用的方式是私有化构造方法方法来避免外部对象new出新的单例对象,但这种方式并不能完全避免创建出新的对象。其实上面的枚举单例中已经提到了,可以通过反射、反序列化等方式,创建出新的对象。

在考虑如何避免通过反射、反序列化创建对象,可以先思考一下,有没有必要去避免?

还是回到最初那个点,我们用了这么多方式来实现单例模式,最终的目的就是为了在进程内的作用范围内只有一个实例。而在代码的开发过程中,我们做好规范和约束,以人为的方式来控制对象的创建数量,哪怕没有私有化构造方法方法也能保证单例。而我们私有化构造方法,更多的是给开发者做出一个提示,这个类是个单例类,并且防止一定的误操作。
可以想象一下,我们要完全将各类创建和初始化的逻辑都“封闭”掉,代码会臃肿到什么地步,而这部分“封闭”的逻辑在业务开发中我们完全使用不到,所以更建议以一种“约定由于配置”的方式来处理单例的创建问题。

综上,做到私有化构造方法这一步就够了,不需要过度开发。

3.2.懒汉式真的比饿汉式更佳吗?

懒汉式主要是为了做懒加载,当单例对象没有使用的时候就不创建和初始化,特别是初始化是需要加载的资源比较多、比较耗时的时候,用懒加载可以加快项目启动的速度,同时又能减少系统的资源浪费。
但是从另一个角度讲,如果不是在启动的时候初始化,那就是在客户端调用的时候初始化,想象一下一个高并发的互联网项目,如果在客户端调用的时候再做耗时的初始化动作,就可能造成接口的请求时间过长,接口超时等,会影响一批用户的使用体验。
另外,如果初始化的过程中存在一些异常情况,我们应该让问题在项目启动时就暴露出来,及时修复,而不是在用户使用的时候才暴露问题。

综上,在业务开发中或许饿汉式单例是更好的选择。

3.3.单例存在的问题

单例模式编写和使用都很简单,但是它也存在一些问题,例如:

  • 面向对象支持不好:单例模式在作用范围内只有一个实例,那就无法通过创建更多的实例来使用面向对象的抽象、继承、多态等特性,更像是一种面向过程的写法。
  • 违反开闭原则:无法拓展,每一次迭代都需要修改原有的代码。
  • 违反单一职责:单例模式既创建对象、又管理对象,职责模糊,可能会导致代码变得复杂。

综上,单例模式存在一定的问题,如果存在拓展的需求就尽可能的避免使用单例模式。

但如果在不需要大量的拓展,又没有业务间的复杂依赖关系,使用单例模式就比较简洁方便也不失为一种选择,例如各种无状态的工具类。

4.其他作用范围的单例模式

4.1.线程内的单例

即单例对象在线程内时唯一的,线程之间不是唯一的,我们开发中有一种很常见的情况:线程局部变量,一般是通过ThreadLocal来做的。
实现原理也比较简单,其实就是使用一个全局的Map来保存对象,以线程对象threadkey,以需要保存的单例对象为value,这样就保证了一个线程只对应一个对象。
在业务流程中的用户登录信息,往往就是保存在ThreadLocal中的,另外PageHelper这个著名的工具类也是通过ThreadLocal来实现的。


如果想了解ThreadLocal的使用方式,可以参考我的另一篇博客《【并发编程】(九)线程安全的代码及ThreadLocal的使用》
如果想了解它详细的实现原理,可以参考《【并发编程】(十)线程局部变量——ThreadLocal原理详解》

4.2.进程间的单例

进程间的单例,更常用的一种说法分布式环境中的单例,这类需求我们使用的也比较多,其实现原理也比较简单,就是将单例对象通过序列化的方式存储在一个多个服务都会共同访问的存储区域中,例如一个共享的文件中、一些分布式的中间件中,例如rediszk等等,而最常见的当然就是分布式锁
我们只需要为单例对象创建出一个唯一标识,在每个服务中判断唯一标识是否存在即可。

5.“多例模式”

多例模式是单例模式中的一种特例,即可以在一定数量范围内创建类的多个实例,还有一层理解就是不同类型的对象可以创建多个,想通类型的对象只能创建一个,后者的概念使用的更多。

以日志打印为例,我们引入Slf4J后通过下面的方式获得一个日志对象:

private Logger logger = LoggerFactory.getLogger(xxx.class);

这里获取的logger如果后面的class对象相同,获取的就是同一个对象,这种方式更像是工厂模式,在代码中看到的也是工厂模式,如下图:
我们进入这个工厂模式的方法后,可以看到下面的代码:
在这里插入图片描述
这里就非常明显了,这就是一种单例模式的创建方式,通过一个Map将单例对象管理起来,如果Map中有就直接返回,如果没有就创建一个并放入到Map中,这里的对象都是logger对象,只是使用日志的类不一样,这就是多例模式的一种体现。

另外,在Spring中如果配置的bean是单例的,其创建方式也与这种方式类似。

6.总结

本来主要讲述了以下几个点:

  • 单例模式的编写方式
    饿汉式、懒汉式、静态内部类、枚举
  • 是否需要严格的禁止单例被破坏:
    没有必要写的太严格,可以通过规范的方式来约束
  • 饿汉式和懒汉式应该如何选择:
    让耗时操作提前初始化,让问题提早暴露,及时修改,而不是让用户去发现
  • 单例模式存在什么问题
    没有面向对象,拓展性差
  • 线程内单例和进程间单例如何实现
    线程内单例通过线程局部变量来实现,进程间的单例通过共享的存储区域来实现
  • 什么叫做“多例模式”
    在一定数量范围内可以创建多个,或者不同的类可以有多个、相同的类只能有一个

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

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

相关文章

jenkins出错与恢复

如果你的jenkins出现了如下图所示问题(比如不能下载插件,无法保存任务等),这个时候就需要重新安装了。 一、卸载干净jenknis 要彻底卸载 Jenkins,您可以按照以下步骤进行操作: 1、停止 Jenkins 服务&…

官方认证:研发效能(DevOps)工程师职业技术认证

培养端到端的研发效能人才 为贯彻落实《关于深化人才发展体制机制改革的意见》,推动实施人才强国战略,促进专业技术人员提升职业素养、补充新知识新技能,实现人力资源深度开发,推动经济社会全面发展,根据《中华人民共…

Vue检测数据的原理

Vue能够对用户的数据进行响应式,也就是你在data中写了什么,你在模板中用到data的部分就会渲染成什么,那么Vue是怎么知道用户修改了data中的数据变化并对模板重新进行解析的呢? 在Vue将数据存储为自身的_data之前,Vue会…

【软考-中级】系统集成项目管理工程师 【18 风险管理】

持续更新。。。。。。。。。。。。。。。 【第十八章】风险管理 (选择3分,偶尔案例18 分 18.1 风险概述18.1.1 风险的定义18.1.2 风险的分类18.1.3 风险的性质 18.2 项目风险管理18.3 规划风险管理18.3.1 规划风险管理的输入18.3.2 规划风险管理的工具与技术18.3.3 …

解决安装nvm以后windows cmd无法找到npm/yarn命令的问题

安装了nodejs多版本管理工具nvm以后,会出现windows cmd无法找到npm/yarn命令的问题 只要一运行npm/yarn就会提示:不是内部命令,找不到运行路径之类的。 解决办法:首先打开windows环境变量的配置,查看NVM_SYMLINK指向…

在nodejs中实现调度任务

在nodejs中实现调度任务 node.js帮助开发人员简化了工作流程,创建了高效的应用程序。它的许多有用功能之一是任务调度。本文将探讨在nodejs中调度任务的重要性、各种使用第三方库的代码示例,以及需要遵循的一些有用的操作。 为什么我们需要安排任务 调…

c语言从入门到实战——C语言数据类型和变量

C语言数据类型和变量 前言1. 数据类型介绍1.1 字符型1.2 整型1.3 浮点型1.4 布尔类型1.5 各种数据类型的长度1.5.1 sizeof操作符1.5.2 数据类型长度1.5.3 sizeof中表达式不计算 2. signed 和 unsigned3. 数据类型的取值范围4. 变量4.1 变量的创建4.2 变量的分类 5. 算术操作符&…

vueday01——ref响应式

特性&#xff1a;持续监控某个响应式变量的属性名变化&#xff0c;可以使用shallowRef来取消这一特性&#xff0c;只监控对象整体的变化 ref测试代码&#xff1a; <template><div :id"idValue" ref"myDiv">打印obj{{ obj }}</div><…

多维时序 | MATLAB实现SSA-CNN-BiLSTM-Attention多变量时间序列预测(SE注意力机制)

多维时序 | MATLAB实现SSA-CNN-BiLSTM-Attention多变量时间序列预测&#xff08;SE注意力机制&#xff09; 目录 多维时序 | MATLAB实现SSA-CNN-BiLSTM-Attention多变量时间序列预测&#xff08;SE注意力机制&#xff09;预测效果基本描述模型描述程序设计参考资料 预测效果 基…

C++项目实战——基于多设计模式下的同步异步日志系统-⑪-日志器管理类与全局建造者类设计(单例模式)

文章目录 专栏导读日志器建造者类完善单例日志器管理类设计思想单例日志器管理类设计全局建造者类设计日志器类、建造者类整理日志器管理类测试 专栏导读 &#x1f338;作者简介&#xff1a;花想云 &#xff0c;在读本科生一枚&#xff0c;C/C领域新星创作者&#xff0c;新星计…

从头开始机器学习:线性回归

从头开始机器学习&#xff1a;线性回归 跟随 16 分钟阅读 28月 <> 1 一、说明 本篇实现线性回归的先决知识是&#xff1a;基本线性代数&#xff0c;微积分&#xff08;偏导数&#xff09;、梯度和、Python &#xff08;NumPy&#xff09;&#xff1b;从线性方程入手。 代…

《机器人学导论》——探究未来世界的奇妙之旅

你是否感到生活变得越来越便利、智能&#xff1f;是的&#xff0c;这些都与机器人技术的发展密不可分。而想要更深入地了解机器人技术&#xff0c;一本好的书籍是必不可少的。那么&#xff0c;今天作者要向大家推荐的就是这样一本优秀的机器人学大作——《机器人学导论》。 《…

JUC并发编程——JUC并发编程概述及Lock锁(重点)(基于狂神说的学习笔记)

基于bilibili狂神说JUC并发编程视频所做笔记 概述 什么是JUC JUC时java.util工具包中的三个包的简称 java.util.concurrent java.util.concurrent.atomic java.util.concurrent.locks 业务&#xff1a;普通的线程代码中&#xff0c;我们常使用Runnable接口 但Runnable没有返…

ElasticSearch-数据查询

ElasticSearch-数据查询 目录概述需求&#xff1a; 设计思路实现思路分析1.查询某索引下的所有数据2.二、条件查询3.、条件查询方式二4.四、分页查询5.五、格式化数据 六、排序七 其他条件执行的成立的查询九、范围查询 参考资料和推荐阅读 Survive by day and develop by nigh…

基于类电磁机制优化的BP神经网络(分类应用) - 附代码

基于类电磁机制优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于类电磁机制优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.类电磁机制优化BP神经网络3.1 BP神经网络参数设置3.2 类电磁机制算法应用 4…

使用Premiere、PhotoShop和Audition做视频特效

今天接到一个做视频的任务&#xff0c;给一个精忠报国的视频&#xff0c;要求&#xff1a;   ①去掉人声&#xff0c;就是将唱歌的人声去掉&#xff0c;只留下伴奏&#xff1b;   ②截图视频中的横幅&#xff0c;做一个展开的效果&#xff0c;类似卷纸慢慢展开&#xff1b;…

使用 Tkinter Canvas 小部件添加放大镜功能?

一、说明 据我所知&#xff0c;内置的 Tkinter Canvas 类比例不会自动缩放图像。如果您无法使用自定义小部件&#xff0c;则可以缩放原始图像并在调用缩放函数时将其替换在画布上。 二、实现图像放大镜技术细节 我如何将放大和缩小添加到以下脚本中&#xff0c;我想将其绑定到…

【狂神说】HTML详解

目录 1 HTML概述1.1 什么是HTML1.2 HTML发展史1.3 HTML5的优势1.4 W3C标准 2 网页2.1 网页基本信息2.2 网页基本标签2.2.1 标题标签2.2.2 段落标签2.2.3 换行标签2.2.4 水平线标签2.2.5 字体样式标签&#xff1a;粗体、斜体2.2.6 注释和特殊符号 2.3 图像标签2.4 链接标签邮箱链…

高质量床上用品类网站带手机端的pbootcms模板

模板介绍&#xff1a; 这是一个基于PbootCMS内核开发的床上用品类网站模板&#xff0c;专为床上用品、家用纺织类企业设计和开发。它不仅提供了网站界面简洁简单、易于管理的特点&#xff0c;还附带了测试数据&#xff0c;方便用户进行演示和学习。 模板特点&#xff1a; 采用…

【TES720D-KIT】青翼自研基于复旦微FMQL20S400全国产化ARM开发套件(核心板+底板)

TES720D-KIT是专门针对我司TES720D&#xff08;基于复旦微FMQL20S400的全国产化ARM核心板&#xff09;的一套开发套件&#xff0c;它包含1个TES720D核心板&#xff0c;加上一个TES720D-EXT扩展底板。 FMQL20S400是复旦微电子研制的全可编程融合芯片&#xff0c;在单芯片内集成…