设计模式(创建型)-单例模式
摘要
在软件开发的世界里,设计模式是开发者们智慧的结晶,它们为解决常见问题提供了经过验证的通用方案。单例模式作为一种基础且常用的设计模式,在许多场景中发挥着关键作用。本文将深入探讨单例模式的定义、实现方式、应用场景以及可能面临的问题与解决方案。
定义
单例模式的核心目标是保证一个类在整个系统中仅有一个实例存在,并且为系统提供一个访问该实例的全局访问点。这种模式在资源管理、数据共享等方面具有重要意义。例如,在一个数据库连接管理系统中,为了避免频繁创建和销毁数据库连接带来的性能开销,使用单例模式确保整个应用程序只有一个数据库连接实例,所有对数据库的操作都通过这个唯一的连接进行。
类图

实现方式
饿汉式
饿汉式单例在类加载时就立即创建唯一的实例对象。代码实现如下:
public class HungrySingleton {// 声明并初始化唯一实例private static final HungrySingleton instance = new HungrySingleton();// 私有构造函数,防止外部实例化private HungrySingleton() {}// 提供全局访问点public static HungrySingleton getInstance() {return instance;}
}
这种方式的优点是线程安全,因为实例在类加载阶段就已创建,而类加载过程由 JVM 保证线程安全。同时,调用效率高,因为不需要额外的同步操作。然而,它的缺点是不能延迟加载。如果该单例对象占用资源较大,而在系统运行过程中可能很长时间都不会用到,那么这种提前创建实例的方式会造成资源浪费。
懒汉式
懒汉式单例是在第一次调用 getInstance 方法时才创建实例。代码如下:
public class LazySingleton {// 声明静态实例,初始值为nullprivate static volatile LazySingleton instance = null;// 私有构造函数private LazySingleton() {}// 同步的获取实例方法public static synchronized LazySingleton getInstance() {if (instance == null) {instance = new LazySingleton();}return instance;}
}
懒汉式的优势在于可以延迟加载,只有在真正需要使用实例时才创建,避免了资源的过早占用。但是,由于 getInstance 方法使用了 synchronized 关键字进行同步,在多线程环境下,每次调用该方法都需要进行同步操作,这会导致调用效率不高。
双重检查锁式
双重检查锁模式旨在解决单例、性能和线程安全问题。代码如下:
public class LazyMan3 {private LazyMan3(){}private static volatile LazyMan3 instance;public static LazyMan3 getInstance(){//第一次判断,如果instance不为null,不需要抢占锁,直接返回对象if (instance == null){synchronized (LazyMan3.class){//第二次判断if (instance == null){instance = new LazyMan3();}}}return instance;}
}
class LazyMan3Test{public static void main(String[] args) {LazyMan3 instance = LazyMan3.getInstance();LazyMan3 instance1 = LazyMan3.getInstance();System.out.println(instance == instance1);}
}
这种方式通过两次 if 判断,在第一次判断实例不为 null 时,直接返回实例,避免了同步操作带来的性能开销。只有当实例为 null 时,才进入同步块进行实例创建。然而,在多线程环境下,由于 JVM 会对实例化对象进行优化和指令重排序操作,可能会出现空指针问题。解决这个问题的方法是使用 volatile 关键字修饰 instance 变量,volatile 可以保证可见性和有序性,防止指令重排序导致的空指针异常。
静态内部类式
静态内部类式单例利用了类加载机制来实现线程安全和延迟加载。代码如下:
public class LazyMan4 {private LazyMan4(){}//定义一个静态内部类private static class LazyMan4Holder{private static final LazyMan4 INSYANCE = new LazyMan4();}//对外访问方法public static LazyMan4 getInstance(){return LazyMan4Holder.INSYANCE;}
}
class LazyMan4Test{public static void main(String[] args) {LazyMan4 instance = LazyMan4.getInstance();LazyMan4 instance1 = LazyMan4.getInstance();System.out.println(instance == instance1);}
}
当外部类 LazyMan4 被加载时,其静态内部类 LazyMan4Holder 并不会立即被加载。只有当调用 getInstance 方法时,LazyMan4Holder 才会被加载,此时会创建 LazyMan4 的唯一实例。这种方式保证了线程安全,因为类加载过程是线程安全的。同时,实现了延迟加载,提高了资源的利用效率。不过,与懒汉式类似,其调用效率相对不高。
静态代码块式
静态代码块式单例在静态代码块中完成实例的初始化。代码如下:
public class HungryChinese2 {//私有构造方法,为了不让外界创建该类的对象private HungryChinese2(){}//声明该类类型的变量private static HungryChinese2 hungryChinese2;//初始值为null//静态代码块中赋值static {hungryChinese2 = new HungryChinese2();}//对外提供的访问方式public static HungryChinese2 getInstance(){return hungryChinese2;}
}
class HungryChinese2Test{public static void main(String[] args) {HungryChinese2 instance = HungryChinese2.getInstance();HungryChinese2 instance1 = HungryChinese2.getInstance();System.out.println(instance.equals(instance1));}
}
这种方式与饿汉式类似,在类加载时通过静态代码块创建实例,因此线程安全,但不能延迟加载。
枚举式
枚举式单例是一种简洁且强大的实现方式。代码如下:
public enum LazyMan5 {INSTANCE;
}
class LazyMan5Test{public static void main(String[] args) {LazyMan5 instance = LazyMan5.INSTANCE;LazyMan5 instance1 = LazyMan5.INSTANCE;System.out.println(instance == instance1);}
}
使用枚举实现单例,不仅线程安全,调用效率高,而且天然地防止了反射和反序列化漏洞。在反序列化时,枚举类型会保证返回的是已有的枚举常量,而不会创建新的对象。不过,它同样不能延迟加载。
应用场景
-
资源管理:如数据库连接池、线程池等资源,使用单例模式可以确保整个系统中只有一个资源实例,避免资源的重复创建和浪费,提高资源的利用率和管理效率。
-
全局配置:系统的全局配置信息,如系统参数、环境变量等,使用单例模式可以方便地在整个系统中访问和修改这些配置,保证配置的一致性。
-
日志记录:日志记录器通常使用单例模式,以便在整个应用程序中记录日志信息。所有的日志记录操作都通过同一个日志记录器实例进行,方便管理和维护日志文件。
-
缓存管理:缓存系统可以使用单例模式来管理缓存实例,确保不同模块对缓存的访问和操作是一致的,提高缓存的命中率和性能。
单例模式可能面临的问题及解决方案
Serializable问题
- 如果单例类实现了
java.io.Serializable接口,在反序列化时可能会出现问题。因为反序列化过程会创建一个新的对象,这可能导致多次反序列化同一对象时得到多个单例类的实例,破坏了单例模式的唯一性。解决方法是在单例类中添加readResolve方法:
private Object readResolve() throws ObjectStreamException {return instance;
}
这样,在反序列化时,如果定义了 readResolve 方法,则直接返回此方法指定的对象,而不会创建新的对象,从而保证了单例的唯一性。
在Android 中使用单例模式可能会内存泄漏
在 Android 开发中,当单例类依赖于 Context 时,如果传入的是 Activity 的 Context,可能会导致内存泄漏。例如:
public class CommUtils {private volatile static CommUtils mCommUtils;private Context mContext;public CommUtils(Context context) {mContext=context;}public static CommUtils getInstance(Context context) {if (mCommUtils == null) {synchronized (CommUtils.class) {if (mCommUtils == null) {mCommUtils = new CommUtils(context);}}}return mCommUtils;}
}
只要这个单例没有被释放,那么持有该单例的 Activity 也不会被释放,直到进程退出。为了解决这个问题,应尽量使用 Application 的 Context,因为 Application 的生命周期伴随着整个进程的周期,不会因为某个 Activity 的销毁而导致单例持有无效的 Context,从而避免内存泄漏。
总结
单例模式在软件开发中具有广泛的应用,不同的实现方式各有优劣。开发者需要根据具体的需求和场景,选择合适的单例实现方式,同时注意解决可能出现的问题,以确保系统的高效、稳定运行。通过合理运用单例模式,可以提高代码的可维护性、可扩展性和性能,为软件项目的成功开发奠定坚实的基础。
相关文章:
设计模式(创建型)-单例模式
摘要 在软件开发的世界里,设计模式是开发者们智慧的结晶,它们为解决常见问题提供了经过验证的通用方案。单例模式作为一种基础且常用的设计模式,在许多场景中发挥着关键作用。本文将深入探讨单例模式的定义、实现方式、应用场景以及可…...
Leetcode 刷题笔记1 图论part01
图论的基础知识: 图的种类: 有向图(边有方向) 、 无向图(边无方向)、加权有向图(边有方向和权值) 度: 无向图中几条边连接该节点,该节点就有几度࿱…...
鸿蒙NEXT开发问题大全(不断更新中.....)
目录 问题1:鸿蒙NEXT获取华为手机的udid 问题2:[Fail]ExecuteCommand need connect-key? 问题3:测试时如何安装app包 问题1:鸿蒙NEXT开发获取华为手机的udid hdc -t "设备的序列号" shell bm get --udid 问题2&…...
分享一个项目中遇到的一个算法题
需求背景: 需求是用户要创建一个任务计划在未来执行,要求在创建任务计划的时候判断选择的时间是否符合要求,否则不允许创建,创建的任务类型有两种,一种是单次,任务只执行一次;另一种是周期&…...
TI的Doppler-Azimuth架构(TI文档)
TI在AWR2944平台上推出新的算法架构,原先的处理方式是做完二维FFT后在RD图上做CFAR检测,然后提取各个通道数据做测角。 Doppler-Azimuth架构则是做完二维FFT后,再做角度维FFT,生成Doppler-Azimuth频谱图,然后在该频谱图…...
电子邮件常用协议技术详解与C++实践(SMTP POP3 IMAP)
一、核心协议概览 协议端口(明文/加密)核心功能数据同步方式典型场景SMTP25 / 587邮件发送单向传输客户端提交邮件POP3110 / 995邮件下载单向同步单设备离线阅读IMAP143 / 993邮件管理双向同步多设备实时同步 二、协议深度解析 1. SMTP(简单…...
机器学习算法:一文掌握 K近邻算法 的详细用法(2个案例可直接运行)
文章目录 一、KNN 算法概述1.1 算法原理1.2 KNN 的优缺点1.3 K 值的选择 二、Python 实现 KNN 案例2.1 使用 KNN 算法进行手写数字识别2.2 使用 Python 实现 KNN 分类 三、总结 KNN(K-Nearest Neighbors,K近邻算法) 是一种简单且常用的分类和…...
设计C语言的单片机接口
一、主要内容 (一)控制引脚 1、定义管脚 // 定义管脚的结构体 struct pin{ int id; // 管脚编号 int mode; // 模式,输入为1,输出为0 int pull; // 输入电阻 int driver; // 功率 } 2、输出电平 语法: void pin_output(s…...
[从零开始学习JAVA] Stream流
前言: 本文我们将学习Stream流,他就像流水线一样,可以对我们要处理的对象进行逐步处理,最终达到我们想要的效果,是JAVA中的一大好帮手,值得我们了解和掌握。(通常和lambda 匿名内部类 方法引用相…...
「自动驾驶的数学交响曲:线性代数、微积分与优化理论的深度共舞」—— 解析人工智能背后的高阶数学工具链
引言 自动驾驶系统是数学工具链的集大成者。从传感器数据的多维空间映射到控制指令的生成,每一步都隐藏着线性代数、微积分、概率论和优化理论的精妙配合。本文将构建一个数学模型完整的自动驾驶案例,结合Python代码实现,揭示以下核心数学工具: 线性代数:张量运算与特征空…...
调试 Rust + WebAssembly 版康威生命游戏
1. 启用 Panic 日志 1.1 让 Panic 信息显示在浏览器控制台 如果 Rust 代码发生 panic!(),默认情况下不会在浏览器开发者工具中显示详细的错误信息。这使得排查问题变得困难。 我们可以使用 console_error_panic_hook 这个 Rust crate,将 Panic 信息打…...
VSCode通过SSH远程登录Windows服务器
系列 1.1 VSCode通过SSH远程登录Windows服务器 1.2 VSCode通过SSH免密远程登录Windows服务器 文章目录 系列1 准备工作2 远程服务器配置2.1 安装SSH服务器2.2 端口 3 本地电脑配置3.1 安装【Remote - SSH】。3.2 登录 1 准备工作 本地电脑Windows 11,已安装VS Cod…...
qt下载和安装教程国内源下载地址
qt不断在更新中,目前qt6日渐成熟,先前我们到官方下载或者国内镜像直接可以下载到exe文件安装,但是最近几年qt官方似乎在逐渐关闭旧版本下载通道,列为不推荐下载。但是qt5以其广泛使用和稳定性,以及积累大量代码使得qt5…...
使用htool工具导出和导入Excel表
htool官网 代码中用到的hool包里面的excel工具ExcelUtil 1. 引入依赖 <!-- Java的工具类 --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.25</version></dependency>&l…...
mysql 到 doris 挪移数据
工具datax..... 下载地址:http://datax-opensource.oss-cn-hangzhou.aliyuncs.com/datax.tar.gz 下载以后解压:tar -xvzf datax.tar.gz 然后,理论上就可以直接使用了。但是,datax本身是python2写的,如果需要python3…...
Springboot中的@ConditionalOnBean注解:使用指南与最佳实践
在使用Spring Boot进行开发时,大家应该都听说过条件注解(Conditional Annotations)。其中的ConditionalOnBean注解就很有趣,它帮助开发者在特定条件下创建和注入Bean,让你的应用更加灵活。今天就来聊聊这个注解的使用场…...
ubuntu系统下添加pycharm到快捷启动栏方法
一、背景 之前在ubuntu系统下使用pycharm时,总是要进入/home/dlut/pycharm-community-2022.1/bin文件夹下,然后终端执行命令下面的命令才可修改代码: ./pycharm.sh为了以后方便,这里给出添加pycharm到快捷启动栏的方法 二、添加…...
开源:LMDB 操作工具:lmcmd
目录 什么是 LMDB为什么编写 lmcmd安装方法如何使用 连接数据库命令列表 小结 1. 什么是 LMDB LMDB(Lightning Memory-Mapped Database)是一种高效的键值存储数据库,基于内存映射(memory-mapping)技术,提供…...
阿里云底层使用的虚拟化技术
阿里云底层使用的虚拟化技术主要是KVM([Kernel-based Virtual Machine)。KVM是一种基于内核的虚拟机技术,它允许Linux内核直接管理虚拟机的创建和运行,提供高效的虚拟化解决方案12。 KVM技术的特点和应用场景 KVM具有以下…...
angular中的路由传参
目录 一、矩阵参数 一、矩阵参数 在angular中传参时可以使用矩阵参数,即直接通过变量值的形式在地址中体现,但需要注意参数的使用范围为当前路径段,而不是全局的查询参数。 const params {name: lhhh,age: 18,list: [{ name: htt }],}; //先…...
AI时代下的心理咨询师新利器:心理咨询小程序
在AI技术日新月异的今天,心理咨询师们也需要与时俱进,借助新型工具来提升咨询效率和服务质量。正如一位优秀的厨师离不开一把锋利的菜刀,心理咨询师同样需要一款得力助手来辅助其工作。而心理咨询小程序,正是这样一款应运而生的工…...
垃圾分类--环境配置
写在前面: 如果你们打这届比赛时,还有我们所保留的内存卡,那么插上即可运行(因为内存卡里我们已经配置好所有的环境) 本文提供两种环境的配置 一种是基于yolov8:YOLOv8 - Ultralytics YOLO Docshttps://d…...
每日一题--计算机网络
一、基础概念类问题 1. TCP 和 UDP 的区别是什么? 回答示例: TCP:面向连接、可靠传输(通过三次握手建立连接,丢包重传)、保证数据顺序(如文件传输、网页访问)。 UDP:无…...
json字符串转对象,对象转JSON
背景: JSON字符串与对象之间的转换。在对接接口的数据的时候,因为是实时数据转发过来的。发现后端发过的数据是字符串【JSON字符串】但是我们前端需要的是一个对象。 核心代码: JSON.parse(JSON字符串) 效果展示: 接口JSON字符串转…...
c++ 基础题目lambda
1. auto lambda = [](double x) { return static_cast<int>(x); }; 是 匿名函数对象 ,不可直接声明 a.可以赋值给一个与其类型兼容的 std::function 类型的对象 std::function<int(int, int)> lambda = [](int x, int y) { return x + y; }; b.使用具体的 lambda …...
pandas中excel自定义单元格颜色
writerpd.ExcelWriter(filepathf05教师固定学生占比1月{today}.xlsx,engineopenpyxl) df.to_excel(writer,sheet_name明细) piv1.to_excel(writer,sheet_name1月分布) wswriter.book.create_sheet(口径) ws.cell(1,1).value综合占比: ws.cell(1,2).value固定学生占比…...
3D标定中的平面约束-平面方程的几何意义
平面方程的一般形式为 AxByCzD0,其中系数 A、B、C、D共同决定了平面的几何特性。 系数对平面姿态的影响 1. 法向量方向2. 平面位置3. 比例关系4. 姿态变换5.平面空间变换 1. 法向量方向 法向量方向由 A、B、C 决定 核心作用:系数 A、B、C 构成的向量 (…...
蓝桥杯第13届真题2
由硬件框图可以知道我们要配置LED 和按键 一.LED 先配置LED的八个引脚为GPIO_OutPut,锁存器PD2也是,然后都设置为起始高电平,生成代码时还要去解决引脚冲突问题 二.按键 按键配置,由原理图按键所对引脚要GPIO_Input 生成代码&a…...
Linux-03 删除ubuntu系统文件夹Videos和Templates后,如何恢复
文章目录 问题解决方法1. 重新创建 Videos 和 Templates 文件夹2. 配置 user-dirs.dirs 文件3. 更新用户目录配置xdg-user-dirs-update4. 重启系统:sudo reboot 问题 手欠的嫌弃Videos和Templates文件夹是空的,也不会用,就删除了,…...
springboot多种生产打包方式教程
在 Spring Boot 项目中,打包是一个非常重要的环节,因为它决定了应用程序如何部署到生产环境中。Spring Boot 提供了多种打包方式,以满足不同的需求和环境。以下是详细的教程,介绍 Spring Boot 的多种生产打包方式以及它们的适用场…...
