【设计模式】Java 设计模式之单例模式(Singleton Pattern)

一、单例模式概述

单例模式是一种创建型设计模式,它确保一个类仅有一个实例,并提供一个全局访问点来访问这个唯一实例。在软件设计中,单例模式常用于管理那些只需要一个实例的类,如配置信息类、数据库连接池等。

二、单例模式结构

单例模式包含以下几个组成部分:

  1. 私有静态实例:用于存储单例对象,通常初始化为null。
  2. 私有构造方法:防止其他类通过new关键字创建该类的实例。
  3. 公有静态方法:提供全局访问点,用于获取单例对象的引用。如果实例不存在,则创建它;如果实例已存在,则直接返回。

三、单例模式的实现方式

单例模式有多种实现方式,这里我们介绍两种常见的实现方式:饿汉式和懒汉式。

  1. 饿汉式实现

饿汉式在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快。

public class Singleton {private static Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}
}
  1. 懒汉式实现

懒汉式在第一次调用getInstance()方法时才进行初始化,所以类加载较快,但获取对象的速度稍慢。需要注意的是,懒汉式实现需要处理多线程并发访问的问题,否则可能会出现多个实例。

线程不安全的懒汉式实现:

public class Singleton {private static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

线程安全的懒汉式实现(使用双重检查锁定):

public class Singleton {private volatile static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}

四、单例模式的优缺点

优点:

  1. 提供了对唯一实例的受控访问。
  2. 由于在系统内存中只存在一个对象,因此可以节约系统资源。
  3. 对于一些需要频繁实例化然后销毁的对象,单例模式可以提高系统性能。

缺点:

  1. 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
  2. 单例类的职责过重,在一定程度上违背了“单一职责原则”。
  3. 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例,可能会导致数据库连接泄露。

五、单例模式的应用场景

单例模式适用于以下场景:

  1. 要求生成唯一序列号的环境。
  2. 在整个项目中需要一个全局访问点来访问某个对象。
  3. 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
  4. 方便资源相互通信的环境,作为资源之间的桥梁。

六、实际应用案例解读

以数据库连接池为例,我们通常会使用单例模式来管理数据库连接。数据库连接池负责创建、管理、释放数据库连接,确保应用程序在需要时能够获取到有效的数据库连接,同时避免频繁地创建和销毁连接,提高系统性能。通过使用单例模式,我们可以确保在整个应用程序中只有一个数据库连接池实例,从而方便管理和维护。

在实际应用中,我们还需要注意单例模式的线程安全性问题。在多线程环境下,需要确保单例对象的创建和访问是线程安全的,避免出现多个实例的情况。同时,我们也需要根据具体的应用场景和需求来选择合适的单例模式实现方式。

七、单例模式的应用案例

以日志记录器(Logger)为例,这是一个常见的单例模式应用场景。在一个大型系统中,我们通常需要记录各种操作和事件,以便进行问题追踪、性能分析等。如果每个需要记录日志的组件都自己创建一个日志记录器对象,那么不仅会浪费资源,还可能导致日志记录的不一致和混乱。因此,我们可以使用单例模式来确保整个系统只有一个日志记录器实例,所有的日志记录操作都通过这个唯一的实例来完成。

具体实现时,我们可以将日志记录器的构造方法设为私有,防止其他类创建新的实例。然后提供一个公有的静态方法,用于获取日志记录器的唯一实例。如果实例已经存在,就直接返回;否则,创建新的实例并返回。

八、单例模式的注意事项

  1. 线程安全性:如前所述,懒汉式单例模式在并发环境下可能会出现多个实例的问题。因此,在实际应用中,我们需要确保单例模式的线程安全性。除了双重检查锁定之外,还可以使用静态内部类、枚举等方式来实现线程安全的单例。
  2. 防止反序列化重新创建对象:在Java中,如果一个类实现了Serializable接口,那么它的对象可以被序列化并反序列化。当反序列化一个单例对象时,会重新创建一个新的对象,从而破坏单例的唯一性。为了解决这个问题,我们可以在单例类中定义一个readResolve()方法,返回单例对象,从而在反序列化时直接返回已有的单例对象,而不是创建新的对象。
  3. 懒加载与饿加载的选择:懒加载与饿加载的主要区别在于实例化的时机。懒加载在第一次使用时才创建实例,而饿加载则在类加载时就创建实例。选择哪种方式取决于具体的应用场景和需求。如果实例的创建开销较大,且系统启动时不需要立即使用该实例,那么可以选择懒加载;否则,可以选择饿加载。

好的,让我们进一步深入单例模式的讨论,探讨一些高级主题和实际应用中的挑战。

九、单例模式的扩展与变体

虽然标准的单例模式提供了很好的基础,但在某些情况下,我们可能需要对其进行扩展或变体设计。

  1. 带参数的单例:标准的单例模式通常不带有参数,因为它是在类加载时或第一次调用时创建的。但如果我们需要根据某些参数来初始化单例对象,那么就需要进行扩展。这通常可以通过将参数放在配置文件中,或者在首次调用getInstance()方法时传入,然后内部进行缓存来实现。

  2. 可销毁的单例:在某些场景中,我们可能需要在应用程序的某个时刻销毁单例对象,并重新创建它。这可以通过在单例类中添加一个destroy()或shutdown()方法来实现,该方法负责清理资源并将单例对象的引用设为null。

  3. 多例模式:虽然这超出了单例模式的范围,但值得一提的是,有时我们可能希望限制一个类的实例数量,而不是仅仅限制为一个。这可以看作是单例模式的一个变体,称为多例模式或有限实例模式。

十、单例模式与依赖注入

在现代软件开发中,依赖注入(Dependency Injection)已经成为一种流行的做法,用于管理对象之间的依赖关系。然而,单例模式与依赖注入之间存在某种程度的冲突。单例模式强调的是全局访问和单一实例,而依赖注入则强调将依赖项作为参数传递给需要它们的对象。

尽管存在这种冲突,但在某些情况下,我们仍然可以将单例对象作为依赖项注入到其他对象中。这通常发生在单例对象作为系统级服务或资源管理器时,例如数据库连接池或日志记录器。

十一、单例模式与Spring框架

在Spring框架中,单例模式是默认的bean作用域。Spring容器负责管理bean的生命周期,确保每个单例bean在整个应用上下文中只有一个实例。这使得单例模式在Spring应用中变得非常简单和透明。

然而,需要注意的是,虽然Spring中的bean默认是单例的,但这并不意味着我们应该滥用单例模式。我们仍然需要根据实际的业务需求和场景来决定是否使用单例。

十二、单例模式的局限性

尽管单例模式在很多场景下都非常有用,但它也有一些局限性。

  1. 测试困难:由于单例对象在全局范围内都是唯一的,这可能导致在单元测试中出现问题。因为测试代码和主代码可能共享同一个单例对象,从而导致测试结果的污染。为了解决这个问题,我们可能需要使用模拟对象(Mocks)或存根对象(Stubs)来替代真实的单例对象。

  2. 代码结构问题:过度使用单例模式可能会导致代码结构变得复杂和难以维护。如果单例对象承担了过多的职责,那么它可能会变得庞大而难以管理。因此,我们应该尽量避免将过多的逻辑和状态封装在单例对象中。

十三、结论

单例模式是一种强大而实用的设计模式,它可以帮助我们管理那些需要全局访问且只需要一个实例的类。然而,它并不是银弹,我们需要根据具体的业务需求和场景来决定是否使用它。同时,我们也需要注意单例模式的局限性和挑战,并采取相应的措施来避免潜在的问题。

在实际应用中,我们应该灵活运用设计模式,结合具体的业务场景和技术栈来做出最佳的设计决策。同时,我们也应该保持学习和探索的态度,不断关注新的设计思想和最佳实践,以提高我们的设计能力和代码质量。

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

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

相关文章

Redis热点数据和冷数据的理解

热点数据,缓存才有价值 对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存, 不仅占用内存,而且价值不大。频繁修改的数据,看情况考虑使用缓存 对于上面两个例子,寿星列 表、导航信息都存在一…

linux网络服务学习(1):nfs

1.什么是nfs NFS:网络文件系统。 *让客户端通过网络访问服务器磁盘中的数据,是一种在linux系统间磁盘文件共享的方法。 *nfs客户端可以把远端nfs服务器的目录挂载到本地。 *nfs服务器一般用来共享视频、图片等静态数据。一般是作为被读取的对象&…

F5怎么样?保障AI服务的安全性和交付

伴随着人工智能时代的快速发展,AI已成为企业数字化转型的得力工具,比如为用户提供更好的服务,降低企业成本等。与此同时,AI技术的应用也会带来应用安全等方面的新风险,可见其有着双刃剑效应。作为一家提供多云应用安全…

【C++】map与set容器的应用总结

当我们处理数据时,有时需要使用一些高效的数据结构来存储和管理元素。在C中,我们有许多与此相关的容器类,如 树型结构:set,map,multiset,multimap; 哈希结构: unordered_set和unordered_map。这些容器提供了…

如何使用ROS和easymqos快速搭建一辆语音控制导航的机器人

之前做的机器人小车基本都属于电脑或手机控制操作。目前,使用语音控制机器人小车运动,让机器人导航去指定地点,已经成为热门,并且语音识别技术已经有落地方案,可满足生活中的基本需要。有些语音芯片通过高算力处理器运…

openGauss学习笔记-244 openGauss性能调优-SQL调优-典型SQL调优点-统计信息调优

文章目录 openGauss学习笔记-244 openGauss性能调优-SQL调优-典型SQL调优点-统计信息调优244.1 统计信息调优244.1.1 统计信息调优介绍244.1.2 实例分析:未收集统计信息导致查询性能差 openGauss学习笔记-244 openGauss性能调优-SQL调优-典型SQL调优点-统计信息调优…

XDP学习笔记

XDP的使用与eBPF程序分不开,因此要了解学历XDP,须知道什么是eBPF、什么是XDP。 概念 eBPF BPF(Berkeley Packet Filter)是一种灵活且高效的数据包过滤技术,最初由 BSD Unix 中的网络子系统引入;BPF 允许用…

WebRTC:真正了解 RTP 和 RTCP

介绍 近年来,通过互联网进行实时通信变得越来越流行,而 WebRTC 已成为通过网络实现实时通信的领先技术之一。WebRTC 使用多种协议,包括实时传输协议 (RTP) 和实时控制协议 (RTCP)。 RTP负责通过网络传输音频和视频数据,而RTCP负责…

植物miRNA数据库PmiREN2.0的使用

前记 miRNA数据库是一个用于存储和分析microRNA(miRNA)序列和相关信息的数据库。miRNA是一类具有约20-24个核苷酸的非编码小RNA分子,通过调控基因表达来参与细胞生物学过程。miRNA数据库中通常包含miRNA序列、miRNA靶向基因、miRNA表达谱、m…

linux系统kubernetes下载可视化工具

kubernetes可视化工具的使用 可视化工具可视化工具的网站部署Kuboard部署Dashboard创建访问账号获取访问令牌浏览器访问 可视化工具 可视化工具的网站 kubesphere官网:https://kubesphere.io/zh/kuboard官网:https://kuboard.cn/install/v3/install-in…

以题为例浅谈文件包含

什么叫做文件包含 文件包含函数加载的参数没有经过过滤或严格定义,可以被用户控制, 包含其他恶意文件,导致了执行非预期代码。 文件包含漏洞(File Inclusion Vulnerability)是一种常见的网络安全漏洞,它允…

2023年总结:一个普通程序员如何挑选出价值千万的职业赛道

​​​​​​​ 引言 随着2023年的序幕缓缓落下,我终于在岁月的流转中捕捉到了一条隐秘而又公开的真理。它悄然告诉我们,成功并非单纯由勤劳的双手雕琢,一份耕耘未必有一份收获,而是在于我们如何在命运的十字路口作出关键选择。那…

Redis如何实现主从复制?主从复制的作用是什么?Redis集群是如何工作的?它有哪些优点和缺点?

Redis如何实现主从复制?主从复制的作用是什么? Redis的主从复制是一种数据复制机制,其中一个Redis实例作为主节点(master),而其他Redis实例作为从节点(slave)。主从复制的实现过程如…

结构体联合体枚举和位段

文章目录 结构体结构体类型的声明特殊的声明 结构的自引用结构体变量的定义和初始化结构体内存对齐为什么要内存对齐结构体传参结构体实现位段(位段的填充&可移植性)位段位段的内存分配空间如何开辟位段的跨平台问题位段的应用 枚举枚举类型的定义枚…

重学SpringBoot3-整合SSM

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-整合SSM Spring Boot整合SSM示例1. 创建Spring Boot项目2. 配置数据源3. 配置MyBatis4. 实现数据访问对象(DAO)5. 编写服务层和控…

Linux-网络基础

目录 数据传输IP地址端口协议网络字节序网络通信--五元组 socket套接字编程udp、tcp区别udp通信程序的编写:套接字接口通信流程:接口代码 字节序转换接口代码实例 发展背景: 最早时期计算机是单机工作,由于两台主机之间无法进行信…

【C++庖丁解牛】List容器的介绍及使用 | 深度剖析 | list与vector的对比

🍁你好,我是 RO-BERRY 📗 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 🎄感谢你的陪伴与支持 ,故事既有了开头,就要画上一个完美的句号,让我们一起加油 目录 1. list的介绍1.1 list的…

CentOS Stream9更改ip地址,网关(设置静态ip)

使用grep命令查询ens160文件所在的文件夹处 grep -rnw /etc -e ens160 然后用vi命令打开文件进行修改 vi /etc/NetworkManager/system-connections/ens160.nmconnection 配置: 假设将ip地址改为192.168.200.130 [connection] idens33 uuid0050f214-01a7-395e-…

环形链表2(C++), test ok

1. 题目 2. 思路分析: 与环形链表1一样,我们需要定义慢指针和快指针,确定链表是否有环,如果链表没有环的话,直接置空即可。如果链表有环,则需要向环形链表1一样,让快指针不断追赶慢指针&#x…

汽车电子零部件(4):行泊一体ADAS

前言: 现阶段智能汽车行业正在大规模力推无限接近于L3的L2++或L2.9自动驾驶量产落地,类似于当初智能手机替换传统手机的行业机会期。智能汽车常见的智能驾驶功能包括: 行车场景:自适应巡航控制ACC;自动变道辅助ALC;交通拥堵辅助TJA;车道居中LCC;领航辅助NOA; 泊车场…