重学设计模式-单例模式

news2025/1/18 7:58:10

一、什么是单例模式

单例模式,从字面意思理解,就是保证一个类只有一个实例,并提供一个全局访问点来访问这个实例。想象一下,在一个大型游戏中,游戏的配置信息类,整个游戏运行期间只需要一份配置数据就够了,没必要创建多个相同的配置实例,这时候单例模式就派上用场了。

它的主要特点有三个:一是私有的构造函数,防止外部代码随意创建类的实例;二是指向唯一实例的私有静态变量;三是一个公有的静态方法,用于获取这个唯一实例。通过这三个要素,单例模式就能够牢牢把控住实例的唯一性。

为了更直观,咱们来看一段简单的 Java 代码示例:

 

public class Singleton {

// 私有静态变量,存放唯一实例

private static Singleton instance;

// 私有构造函数,阻止外部实例化

private Singleton() {}

// 公有静态方法,获取唯一实例

public static Singleton getInstance() {

if (instance == null) {

instance = new Singleton();

}

return instance;

}

}

在这段代码中,private static Singleton instance 就是那个保存唯一实例的 “秘密基地”,private Singleton() 构造函数上了一把 “锁”,不让外人随便闯入创建新对象,而 public static Singleton getInstance() 方法则像是一个 “门卫”,当有人需要这个实例时,它负责检查并提供。

[此处可以插入一张简单示意单例模式类结构的 UML 图,帮助读者视觉化理解,图大概展示类中有私有静态变量、私有构造函数、公有静态方法这几个关键元素,以及它们之间的关系]

二、单例模式的优点

  1. 节约系统资源

由于只存在一个实例,避免了重复创建对象带来的内存开销。比如说,在一个电商系统里,数据库连接池通常采用单例模式。创建数据库连接是比较耗费资源的操作,如果每次数据库操作都新建一个连接,系统资源很快就会被耗尽。而单例模式下,整个系统共享一个连接池实例,大大节省了资源,提高了系统的性能和稳定性。

  1. 全局唯一访问点

提供统一的访问入口,使得代码逻辑更加清晰。以操作系统中的任务管理器为例,无论在系统的哪个模块、哪个层级,当需要获取当前系统运行状态信息时,都可以通过任务管理器这个单例的全局访问点来获取,不用四处寻找不同的数据源,方便又可靠。

三、单例模式的实现方式

  1. 懒汉式(线程不安全)

咱们前面看到的示例代码其实就是懒汉式单例模式的一种简单实现。它的特点是在第一次调用 getInstance 方法时才去创建实例,也就是 “懒加载”,延迟了实例的创建时机。但这种方式在多线程环境下是不安全的。想象一下,多个线程同时进入 if (instance == null) 判断,都以为还没有实例,就会各自创建一个实例,这就违背了单例模式的初衷。

为了解决线程不安全问题,就有了下面的改进版。

  1. 懒汉式(线程安全)
 

public class ThreadSafeSingleton {

private static ThreadSafeSingleton instance;

private ThreadSafeSingleton() {}

public static synchronized ThreadSafeSingleton getInstance() {

if (instance == null) {

instance = new ThreadSafeSingleton();

}

return instance;

}

}

这里通过给 getInstance 方法加上 synchronized 关键字,使得在同一时刻只有一个线程能够进入这个方法,保证了多线程环境下的单例性。不过,这种方式的缺点是性能开销较大,因为每次调用 getInstance 方法都要获取锁,即使实例已经创建好了,也会有额外的开销。

  1. 饿汉式
 

public class EagerSingleton {

// 在类加载时就创建实例

private static final EagerSingleton instance = new EagerSingleton();

private EagerSingleton() {}

public static EagerSingleton getInstance() {

return instance;

}

}

与懒汉式不同,饿汉式在类加载阶段就创建好了实例,天生就是线程安全的,因为类加载过程由 JVM 保证是线程安全的。但它的缺点是可能会造成资源浪费,如果这个单例实例在程序运行很长一段时间后才会被用到,那么在前期就占用了内存空间。

  1. 双重检查锁(DCL)
 

public class DoubleCheckedLockingSingleton {

private static volatile DoubleCheckedLockingSingleton instance;

private DoubleCheckedLockingSingleton() {}

public static DoubleCheckedLockingSingleton getInstance() {

if (instance == null) {

synchronized (DoubleCheckedLockingSingleton.class) {

if (instance == null) {

instance = new DoubleCheckedLockingSingleton();

}

}

}

return instance;

}

}

双重检查锁模式结合了懒汉式的延迟加载优势和一定的性能优化。首先检查实例是否已经存在,如果不存在,再进入同步块进行二次检查并创建实例。这里的 volatile 关键字很关键,它保证了变量的可见性,防止指令重排序导致的线程安全问题。在多线程高并发场景下,这种方式能在保证单例的同时,尽量减少性能损耗。

  1. 静态内部类
 

public class StaticInnerClassSingleton {

private StaticInnerClassSingleton() {}

private static class SingletonHolder {

private static final StaticInnerClassSingleton instance = new StaticInnerClassSingleton();

}

public static StaticInnerClassSingleton getInstance() {

return SingletonHolder.instance;

}

}

这种方式利用了 Java 的静态内部类特性,当外部类被加载时,静态内部类不会立即加载,只有在调用 getInstance 方法时,静态内部类才会加载并创建实例,实现了延迟加载。同时,由于类加载机制保证了线程安全性,所以它既高效又线程安全,是一种比较推荐的实现方式。

[每介绍一种实现方式,都可以插入对应的简单示意代码执行流程的图片,比如用序列图展示多线程环境下不同实现方式中线程获取实例的过程,帮助读者更好理解代码运行逻辑]

四、单例模式的应用场景

  1. 日志记录器

在一个复杂的软件系统中,需要记录系统运行过程中的各种信息,如错误日志、操作日志等。使用单例模式的日志记录器可以保证整个系统的日志输出到同一个地方,方便管理和查看。不同模块只需调用日志记录器的单例实例,就能统一地记录日志,不会出现日志分散在各处,难以追踪的问题。

  1. 配置文件读取

软件通常需要读取配置文件来获取运行参数,像数据库连接字符串、服务器端口号等。配置文件读取类采用单例模式,确保整个系统使用的是同一套配置数据,避免因配置不一致导致的错误。而且,只需要在第一次使用时读取配置文件并缓存数据,后续直接从单例实例中获取,提高了效率。

  1. 线程池

线程池负责管理和调度线程,在多线程应用中,一个系统通常只需要一个线程池。通过单例模式创建线程池,能够统一分配线程资源,控制并发数量,防止线程创建过多导致系统崩溃,保障系统的稳定运行。

  1. 缓存管理

比如网页缓存,为了提高页面加载速度,会缓存已经访问过的页面内容。缓存管理器作为单例,可以全局控制缓存的存储、检索和清理,确保不同页面请求能高效共享缓存资源,减少重复的数据获取和处理。

五、单例模式的注意事项

  1. 生命周期管理

要注意单例对象的生命周期与应用程序的生命周期是否匹配。如果单例对象持有一些其他资源,在应用程序关闭时,需要确保这些资源被正确释放,否则可能导致资源泄露。

  1. 多线程并发

在多线程环境下,一定要选择合适的单例实现方式,避免出现线程安全问题。错误的实现可能会导致多个实例被创建,破坏单例模式的完整性,进而引发程序逻辑错误。

  1. 单元测试挑战

由于单例模式的特性,对依赖单例的代码进行单元测试时可能会遇到困难。比如,单例实例在全局只有一个,测试不同场景下的代码逻辑时,难以模拟不同的单例状态。这时候就需要一些特殊的测试技巧,如使用依赖注入框架,在测试时能够替换单例实例,方便进行单元测试。

总之,单例模式是编程中一个非常实用的设计模式,它在节约资源、提供统一访问等方面有着显著优势。但在使用过程中,要根据具体场景选择合适的实现方式,并注意相关的注意事项,才能让单例模式真正为我们的软件项目增光添彩。希望通过这篇博客,大家都能对单例模式有一个深入的了解,并能在自己的编程之旅中灵活运用。

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

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

相关文章

BUUCTF Web

[极客大挑战 2019]LoveSQL union注入 是sql注入类型 输入1 发现不是数字型注入,那就是字符型注入。判断字段数,输入order by 4 #发现错误,就存在三个字段数 判断回显点:1 union select 1,2,3 # 判断回显点为2,3 判断数据库名 …

Kinova仿生机械臂Gen3搭载BOTA 力矩传感器SeneOne:彰显机器人触觉 AI 与六维力传感的融合力量

随着工业4.0时代的到来,自动化和智能化成为制造业的趋势。机器人作为实现这一趋势的重要工具,其性能和智能水平直接影响到生产效率和产品质量。然而,传统的机器人系统在应对复杂任务时往往缺乏足够的灵活性和适应性。为了解决这一问题&#x…

【数据库】MySQL数据库SQL语句汇总

目录 1.SQL 通用语法 2.SQL 分类 2.1.DDL 2.2.DML 2.3.DQL 2.4.DCL 3.DDL 3.1.数据库操作 3.1.1.查询 3.1.2.创建 3.1.3.删除 3.1.4.使用 3.2.表操作 3.2.1.查询 3.2.2.创建 3.2.3.数据类型 3.2.3.1.数值类型 3.2.3.2.字符串类型 3.2.3.3.日期时间类型 3.2…

《汽车与驾驶维修》是什么级别的期刊?是正规期刊吗?能评职称吗?

​问题解答: 问:《汽车与驾驶维修》是不是核心期刊? 答:不是,是知网收录的第二批认定学术期刊。 问:《汽车与驾驶维修》级别? 答:省级。主管单位:中国机械工业联合会…

鸿蒙UI(ArkUI-方舟UI框架)-开发布局

文章目录 开发布局1、布局概述1)布局结构2)布局元素组成3)如何选择布局4)布局位置5)对子元素的约束 2、构建布局1)线性布局 (Row/Column)概述布局子元素在排列方向上的间距布局子元素在交叉轴上的对齐方式(…

数据结构——概述

1、什么是数据结构? 数据结构是计算机存储和管理数据的方式。数据必须依据某种逻辑联系组织在一起存储在计算机内,数据结构研究的就是这种数据的逻辑结构和数据的存储结构 2、逻辑结构——数据本身之间的关系 逻辑结构在计算机中的实现 (1…

业务架构、数据架构、应用架构和技术架构

TOGAF(The Open Group Architecture Framework)是一个广泛应用的企业架构框架,旨在帮助组织高效地进行架构设计和管理。 TOGAF 的核心就是由我们熟知的四大架构领域组成:业务架构、数据架构、应用架构和技术架构。 企业数字化架构设计中的最常见要素是4A 架构。 4…

python爬虫入门(实践)

python爬虫入门(实践) 一、对目标网站进行分析 二、博客爬取 获取博客所有h2标题的路由 确定目标,查看源码 代码实现 """ 获取博客所有h2标题的路由 """url "http://www.crazyant.net"import re…

简历_使用优化的Redis自增ID策略生成分布式环境下全局唯一ID,用于用户上传数据的命名以及多种ID的生成

系列博客目录 文章目录 系列博客目录WhyRedis自增ID策略 Why 我们需要设置全局唯一ID。原因:当用户抢购时,就会生成订单并保存到tb_voucher_order这张表中,而订单表如果使用数据库自增ID就存在一些问题。 问题:id的规律性太明显、…

win32汇编环境,窗口程序中对多行编辑框的操作

;运行效果 ;win32汇编环境,窗口程序中对多行编辑框的操作 ;比如生成多行编辑框,显示文本、获取文本、设置滚动条、捕获超出文本长度消息等。 ;直接抄进RadAsm可编译运行。重点部分加备注。 ;下面为asm文件 ;>>>>>>>>>>>>>&g…

【Flink系列】5. DataStream API

5. DataStream API DataStream API是Flink的核心层API。一个Flink程序,其实就是对DataStream的各种转换。具体来说,代码基本上都由以下几部分构成: 5.1 执行环境(Execution Environment) Flink程序可以在各种上下文…

探索未来:Leap Motion JavaScript框架——开启VR与手势控制的无限可能

探索未来:Leap Motion JavaScript框架——开启VR与手势控制的无限可能 leapjs JavaScript client for the Leap Motion Controller 项目地址: https://gitcode.com/gh_mirrors/le/leapjs 项目介绍 欢迎来到Leap Motion JavaScript框架的世界!Lea…

PCM5142集成32位384kHz PCM音频立体声114dB差分输出DAC编解码芯片

目录 PCM5142 简介PCM5142功能框图PCM5142特性 参考原理图 PCM5142 简介 PCM514x 属于单片 CMOS 集成电路系列,由立体声数模转换器 (DAC) 和采用薄型小外形尺寸 (TSSOP) 封装的附加支持电路组成。PCM514x 使用 TI 最新一代高级分段 DAC 架构产品,可实现…

技术领衔 互学互鉴|ZASM召开2024年度技术交流会

1月16日,ZASM组织召开了“2024年度企业员工技术交流活动”。公司总经理,技术部门负责人及项目经理参加本次会议。 会上,公司所属各项目技术负责人围绕“三维模型切割模块的基础操作与模型发布缓存的技术演示”、“J18微型智能空中作业平台的…

UI自动化测试:异常截图和page_source

自动化测试过程中,是否遇到过脚本执行中途出错却不知道原因的情况?测试人员面临的不仅是问题的复现,还有对错误的快速定位和分析。而异常截图与页面源码(Page Source)的结合,正是解决这一难题的利器。 在实…

OSI七层协议——分层网络协议

OSI七层协议,顾名思义,分为七层,实际上七层是不存在的,是人为的进行划分,让人更好的理解 七层协议包括,物理层(我),数据链路层(据),网络层(网),传输层(传输),会话层(会),表示层(表),应用层(用)(记忆口诀->我会用表…

浅谈计算机网络04 | 现代网络需求与技术支撑

现代网络需求与技术支撑 一、网络和因特网流量的类型剖析1.1 弹性流量的自适应特征1.2 非弹性流量的刚性特征1.3 实时流量特性 二、特定领域的网络需求解析2.1 大数据环境下的网络需求分析2.2 云计算环境下的网络需求分析2.3 移动数据环境下的网络需求分析 三、QoS和QoE&#x…

微服务架构下的负载均衡:Spring Cloud如何实现高效流量分配

在Spring Cloud中,实现服务的负载均衡,主要是为了让多个服务实例能够均匀分担请求压力,就像把一堆东西分给几个人拿,确保大家都不太累。 假设你开了一个网店,有很多顾客会同时来买东西(这就是并发请求&…

L3自动驾驶开始落地,AI交通时代离我们有多远?

2025年,自动驾驶领域迎来了一个重要的里程碑——L3级别自动驾驶技术的逐步落地。据《中国汽车报》报道,多家汽车制造商已获得L3级自动驾驶的量产资质,这意味着车辆能够在特定条件下完全接管驾驶任务,而驾驶员可以在车内进行其他活…

动手学大数据-2常见的查询优化器

目录 什么是查询优化器 查询优化器分类 Top-downOptimizer Bottom-upOptimizer RBO-关系代数 RBO-优化原则 RBO-列裁剪 RBO-谓词下推 RBO-传递闭包 RBO-RuntimeFilter 小结 CBO(Cost-basedOptimizer) 概念 CBO-统计信息 CBO-统计信息…