【设计模式】单例模式、“多例模式”的实现以及对单例的一些思考
文章目录
- 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来保存对象,以线程对象thread为key,以需要保存的单例对象为value,这样就保证了一个线程只对应一个对象。
在业务流程中的用户登录信息,往往就是保存在ThreadLocal中的,另外PageHelper这个著名的工具类也是通过ThreadLocal来实现的。
如果想了解ThreadLocal的使用方式,可以参考我的另一篇博客《【并发编程】(九)线程安全的代码及ThreadLocal的使用》
如果想了解它详细的实现原理,可以参考《【并发编程】(十)线程局部变量——ThreadLocal原理详解》
4.2.进程间的单例
进程间的单例,更常用的一种说法分布式环境中的单例,这类需求我们使用的也比较多,其实现原理也比较简单,就是将单例对象通过序列化的方式存储在一个多个服务都会共同访问的存储区域中,例如一个共享的文件中、一些分布式的中间件中,例如redis、zk等等,而最常见的当然就是分布式锁。
我们只需要为单例对象创建出一个唯一标识,在每个服务中判断唯一标识是否存在即可。
5.“多例模式”
多例模式是单例模式中的一种特例,即可以在一定数量范围内创建类的多个实例,还有一层理解就是不同类型的对象可以创建多个,想通类型的对象只能创建一个,后者的概念使用的更多。
以日志打印为例,我们引入Slf4J后通过下面的方式获得一个日志对象:
private Logger logger = LoggerFactory.getLogger(xxx.class);
这里获取的logger如果后面的class对象相同,获取的就是同一个对象,这种方式更像是工厂模式,在代码中看到的也是工厂模式,如下图:
我们进入这个工厂模式的方法后,可以看到下面的代码:

这里就非常明显了,这就是一种单例模式的创建方式,通过一个Map将单例对象管理起来,如果Map中有就直接返回,如果没有就创建一个并放入到Map中,这里的对象都是logger对象,只是使用日志的类不一样,这就是多例模式的一种体现。
另外,在Spring中如果配置的bean是单例的,其创建方式也与这种方式类似。
6.总结
本来主要讲述了以下几个点:
- 单例模式的编写方式
饿汉式、懒汉式、静态内部类、枚举 - 是否需要严格的禁止单例被破坏:
没有必要写的太严格,可以通过规范的方式来约束 - 饿汉式和懒汉式应该如何选择:
让耗时操作提前初始化,让问题提早暴露,及时修改,而不是让用户去发现 - 单例模式存在什么问题
没有面向对象,拓展性差 - 线程内单例和进程间单例如何实现
线程内单例通过线程局部变量来实现,进程间的单例通过共享的存储区域来实现 - 什么叫做“多例模式”
在一定数量范围内可以创建多个,或者不同的类可以有多个、相同的类只能有一个
相关文章:
【设计模式】单例模式、“多例模式”的实现以及对单例的一些思考
文章目录 1.概述2.单例模式实现代码2.1.饿汉式单例2.2.懒汉式单例2.3.双检锁单例2.4.静态内部类单例2.5.枚举单例 3.对单例的一些思考3.1.是否需要严格的禁止单例被破坏?3.2.懒汉式真的比饿汉式更佳吗?3.3.单例存在的问题 4.其他作用范围的单例模式4.1.线…...
idea 2022 一个工作空间下导入git项目 后 无法导入第二个git项目
idea 2022 一个工作空间下导入git项目 后 无法导入第二个git项目 如图所示 我导入了一个git项目后,菜单栏出现了一个git按钮 找不到 导入git项目的按钮了 方式1、 通过idea设置 打开全局设置 如下图 把git先改为none,保存 保存后就可以看到 VCS按钮 导入…...
泛在电力物联网的关键技术与未来发展策略-安科瑞黄安南
摘要: 文章分析了泛在电力物联网的内涵及其主要特征,针对泛在电力物联网的建设目标、基本构架以及关键技术与未来发展策略进行综合探讨,期待得到专业人士的指点。 关键词: 泛在电力物联网, 网络规划, 网络发展 随着能源革命的不…...
iWall:支持自定义的Mac动态壁纸软件
iWall Mac是一款动态壁纸软件,它可以使用任何格式的漂亮视频(无须转换)、图片、动画、Flash、gif、swf、程序、网页、网站做为您的动态壁纸、动态桌面,并且可以进行交互。 这款软件功能多、使用简单、体积小巧、不占用资源、运行…...
【Docker 内核详解】namespace 资源隔离(四):Mount namespace Network namespace
【Docker 内核详解 - namespace 资源隔离】系列包含: namespace 资源隔离(一):进行 namespace API 操作的 4 种方式namespace 资源隔离(二):UTS namespace & IPC namespacenamespace 资源隔…...
STM32简介
STM32是ST公司基于ARM Cortex-M内核开发的32位微控制器,常应用在嵌入式领域如: 智能车(用stm32做寻迹小车,读取光电传感器或者摄像头数据,然后驱动电机前进和转弯); 无人机(用stm3…...
Yum安装JDK11
一、安装命令 : yum install java-11-openjdk二、执行以下命令来查看 JDK 11 的安装信息: yum list installed | grep java-11-openjdk三、找到 JDK 11 的软件包名称(使用以下命令来查询软件包的安装位置): rpm -ql…...
[HNCTF 2022 WEEK2]ez_ssrf题目解析
这题主要是引入ssrf这个漏洞攻击,本质上没有更深入的考察 本题是需要我们去伪造一个ssrf的请求头去绕过 题目开始给了我们信息让我们去访问index.php fsockopen函数触发ssrf fsockopen() 函数建立与指定主机和端口的 socket 连接。然后,它将传入的 bas…...
OpenFOAM: twoPhaseEulerFoam解读
twoPhaseEulerFoam全解读之一(转载) 本系列将对OpenFOAM-2.1.1 中的 twoPhaseEulerFoam 求解器进行完全解读,共分三部分:方程推导,代码解读,补充说明。本篇进行方程推导,详细介绍如果从双流体模型出发得到 twoPhaseEu…...
ffmpeg跨平台arm编译-ubuntu
目录 1. 安装必要的编译器2. 安装必要的依赖项3. 配置编译选项4. 编译安装 1. 安装必要的编译器 32位系统: sudo apt-get update sudo apt-get install gcc-arm-linux-gnueabihf sudo apt-get install g-arm-linux-gnueabihf64位系统: sudo apt-get u…...
Vue 网络处理 - axios 异步请求的使用,请求响应拦截器
目录 一、axiox 1.1、axios 简介 1.2、axios 基本使用 1.2.1、下载核心 js 文件. 1.2.2、发送 GET 异步请求 1.2.3、发送 POST 异步请求 1.2.4、发送 GET、POST 请求最佳实践 1.3、请求响应拦截器 1.3.1、拦截器解释 1.3.2、请求拦截器的使用 1.3.3、响应拦截器的使用…...
单目3D目标检测——MonoDLE 模型训练 | 模型推理
本文分享 MonoDLE 的模型训练、模型推理、可视化3D检测结果。 模型原理,参考我这篇博客:【论文解读】单目3D目标检测 MonoDLE(CVPR2021)_一颗小树x的博客-CSDN博客 源码地址:https://github.com/xinzhuma/monodle 目…...
CSS悬停卡片翻转明信片效果源码附注释
运行效果演示: HTML页面代码: <!DOCTYPE html> <html lang="en" > <head>...
使用kaliber与imu_utils进行IMU、相机+IMU联合标定
目录 1 标定工具编译 1.1 IMU标定工具 imu_utils 1.2 相机标定工具 kaliber 2 标定数据录制 3 开始标定 3.1 IMU标定 3.2 相机标定 3.3 相机IMU联合标定 4 将参数填入ORBSLAM的文件中 1 标定工具编译 1.1 IMU标定工具 imu_utils 标定IMU我们使用imu_utils软件进行标定…...
统一观测丨使用 Prometheus 监控 SQL Server 最佳实践
作者:啃唯 SQL Server 简介 SQL Server 是什么? Microsoft SQL Server 是 Microsoft 推出的关系型数据库解决方案,支持企业 IT 环境中的各种事务处理、商业智能和分析应用程序。Microsoft SQL Server 是市场领先的数据库技术之一。 SQL S…...
最短无序连续子数组
题目链接 最短无序连续子数组 题目描述 注意点 找出符合题意的 最短 子数组,并输出它的长度-100000 < nums[i] < 100000 解答思路 本题的数组可以分为三段,左段中段和右段,如下图所示 观察规律可知,左段元素始终比中段…...
更新 | 持续开源迅为RK3568驱动指南第十二篇-GPIO子系统
《iTOP-RK3568开发板驱动开发指南》更新,本次更新内容对应的是驱动(第十二期_GPIO子系统-全新升级)视频,后续资料会不断更新,不断完善,帮助用户快速入门,大大提升研发速度。 文档教程更新至第十…...
centos7安装erlang23.3.4.11及rabbitmq3.9.16版本
rpm包有系统版本要求,el是Red Hat Enterprise Linux(EL)的缩写。 EL7是Red Hat 7.x,Centos 7.x EL8是Red Hat 8.x, Centos 8.x 所以我们在安装erlang及rabbitmq时需要选择与自己的服务器相对应的rpm包 # rabbitmq的rpm安装包 https://github.com/rabbi…...
VMware和Debian下载
文章目录 ⭐️写在前面的话⭐️一、VMware二、Debain三、建立虚拟机🚀 先看后赞,养成习惯!🚀🚀 先看后赞,养成习惯!🚀 ⭐️写在前面的话⭐️ CSDN主页:程序员好冰 目前在…...
mysql面试题48:MySQL中 Innodb的事务与日志的实现方式
该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官: Innodb的事务与日志的实现方式 以下是InnoDB事务和日志的实现方式的详细说明: 事务日志(Transaction Log): InnoDB使用事务日志来保证事务的…...
Eino:Agent的LLM抽象
拨开迷雾看本质:从零推导 ChatModelAgent(模型适配层与 Agent 运行时) 在 react.md 里看到的是 “ReAct 作为范式” 的推导;而本篇把视角切到 chatmodel.go 作为工程实现:它不只是“为了 ReAct 画图”,更是…...
Go HTTP Server 性能分析与优化
Go HTTP Server 性能分析与优化 在当今高并发的互联网应用中,HTTP Server的性能直接决定了用户体验和系统稳定性。Go语言凭借其轻量级协程和高效的网络库,成为构建高性能HTTP服务的首选之一。即使使用Go,开发者仍需深入分析性能瓶颈并进行针…...
lingbot-depth-pretrain-vitl-14效果展示:多光照/反光表面深度补全自然边缘案例
lingbot-depth-pretrain-vitl-14效果展示:多光照/反光表面深度补全自然边缘案例 1. 引言:当深度图遇上“反光杀手” 你有没有遇到过这种情况?用深度相机扫描一个光滑的桌面,或者对着窗户拍一张照片,结果生成的深度图…...
绕过RK3588的RGA坑:手把手教你修改YOLOv8分割模型部署代码,用CPU预处理替代硬件加速
RK3588部署YOLOv8分割模型的稳定化实践:从RGA报错到CPU预处理方案优化 当你在RK3588开发板上部署YOLOv8分割模型时,是否遇到过这样的场景:模型转换和交叉编译一切顺利,却在运行时突然弹出"Failed to call RockChipRga interf…...
深度学习计算机视觉:从原理到实践
深度学习计算机视觉:从原理到实践 1. 背景与动机 计算机视觉是深度学习最成功的应用领域之一。从图像分类到目标检测,从语义分割到图像生成,深度学习技术已广泛应用于自动驾驶、医疗影像、工业检测等领域。本文将介绍计算机视觉的核心技术和实…...
企业内部AI定制哪家强?
企业内部AI定制,难道不是数谷智能科技的核心专长吗?在2026年的大湾区商业版图中,“数字化”早已不再是选择题,而是关乎生存的底座。从深圳龙岗的跨境电商写字楼,到东莞松山湖的精密智造车间,管理者们正面临…...
西门子1517F与KTP精致屏:主机厂程序框架探秘
西门子1517F搭配KTP精致屏 主机厂程序框架模板,程序结构清晰,主流程采用顺控器编写,包含各种常用功能块的手自动以及手动调用,HMI画面多采用多路复用,大大缩短编程时间, 组态从站有拧紧枪,以及从…...
SLAM Toolbox:从零开始掌握机器人自主定位与建图技术
SLAM Toolbox:从零开始掌握机器人自主定位与建图技术 【免费下载链接】slam_toolbox Slam Toolbox for lifelong mapping and localization in potentially massive maps with ROS 项目地址: https://gitcode.com/gh_mirrors/sl/slam_toolbox 在机器人技术飞…...
在大数据领域发挥 RabbitMQ 的消息队列流量控制策略
在大数据领域发挥 RabbitMQ 的消息队列流量控制策略关键词:RabbitMQ、消息队列、流量控制、大数据、QoS、背压机制、负载均衡摘要:本文深入探讨如何在大数据场景下有效利用RabbitMQ的消息队列流量控制策略。我们将从RabbitMQ的核心架构出发,详…...
深度解析:PaperZZ AI 如何把 “文献综述难产” 变成 “10 分钟定稿”?
Paperzz-AI官网免费论文查重复率AIGC检测/开题报告/文献综述/论文初稿/期刊论文paperzz - 文献综述https://www.paperzz.cc/journalsReviewed 提到本科毕业论文,比起提笔写正文,绝大多数同学的噩梦都是文献综述。这不仅是论文的开篇,更是评判…...
