设计模式(创建型)-单例模式

摘要        

        在软件开发的世界里,设计模式是开发者们智慧的结晶,它们为解决常见问题提供了经过验证的通用方案。单例模式作为一种基础且常用的设计模式,在许多场景中发挥着关键作用。本文将深入探讨单例模式的定义、实现方式、应用场景以及可能面临的问题与解决方案。

定义

        单例模式的核心目标是保证一个类在整个系统中仅有一个实例存在,并且为系统提供一个访问该实例的全局访问点。这种模式在资源管理、数据共享等方面具有重要意义。例如,在一个数据库连接管理系统中,为了避免频繁创建和销毁数据库连接带来的性能开销,使用单例模式确保整个应用程序只有一个数据库连接实例,所有对数据库的操作都通过这个唯一的连接进行。

类图

实现方式

饿汉式

        饿汉式单例在类加载时就立即创建唯一的实例对象。代码实现如下:

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);}
}

        使用枚举实现单例,不仅线程安全,调用效率高,而且天然地防止了反射和反序列化漏洞。在反序列化时,枚举类型会保证返回的是已有的枚举常量,而不会创建新的对象。不过,它同样不能延迟加载。

应用场景

  1. 资源管理:如数据库连接池、线程池等资源,使用单例模式可以确保整个系统中只有一个资源实例,避免资源的重复创建和浪费,提高资源的利用率和管理效率。

  2. 全局配置:系统的全局配置信息,如系统参数、环境变量等,使用单例模式可以方便地在整个系统中访问和修改这些配置,保证配置的一致性。

  3. 日志记录:日志记录器通常使用单例模式,以便在整个应用程序中记录日志信息。所有的日志记录操作都通过同一个日志记录器实例进行,方便管理和维护日志文件。

  4. 缓存管理:缓存系统可以使用单例模式来管理缓存实例,确保不同模块对缓存的访问和操作是一致的,提高缓存的命中率和性能。

单例模式可能面临的问题及解决方案

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,从而避免内存泄漏。

总结

        单例模式在软件开发中具有广泛的应用,不同的实现方式各有优劣。开发者需要根据具体的需求和场景,选择合适的单例实现方式,同时注意解决可能出现的问题,以确保系统的高效、稳定运行。通过合理运用单例模式,可以提高代码的可维护性、可扩展性和性能,为软件项目的成功开发奠定坚实的基础。

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

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

相关文章

基于FPGA频率、幅度、相位可调的任意函数发生器(DDS)实现

基于FPGA实现频率、幅度、相位可调的DDS 1 摘要 直接数字合成器( DDS ) 是一种通过生成数字形式的时变信号并进行数模转换来产生模拟波形(通常为正弦波)的方法,它通过数字方式直接合成信号,而不是通过模拟信号生成技术。DDS主要被应用于信号生成、通信系统中的本振、函…

本地JAR批量传私服

在有网络隔离的环境下,Maven项目如果没有搭建私服就得把用到的通用组件通过U盘在每个组员间拷贝来拷贝去。非常的麻烦跟低效。搭建私服,如果通用组件很多的时候手工一个一个上传更是非常的麻烦跟低效; 我就遇上这问题,跟A公司合作…

【ROS实战】02-ROS架构介绍

1. 简介 你是否曾有过这样的疑问:我按照文档安装了ROS,依照要求写了一些示例节点(node)、消息(msg)和话题(topic),但觉得过程既麻烦又繁琐。也许你开始怀疑:…

LeetCode算法题(Go语言实现)_07

题目 给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复…

网络华为HCIA+HCIP 网络编程自动化

telnetlib介绍 telnetlib是Python标准库中的模块。它提供了实现Telnet功能的类telnetlib.Telnet。这里通过调用telnetlib.Telnet类里的不同方法实现不同功能。 配置云

查看GPU型号、大小;CPU型号、个数、核数、内存

GPU型号、大小 nvidia-smiCPU型号 cat /proc/cpuinfo | grep model name | uniqCPU个数 cat /proc/cpuinfo | grep "physical id" | uniq | wc -lCPU核数 cat /proc/cpuinfo | grep "cpu cores" | uniqCPU内存 cat /proc/meminfo | grep MemTotal参考…

Docker与K8S是什么该怎么选?

用了很久的容器化,最近突然看到一个问题问: docker和K8S究竟有什么区别,到底该怎么选?我认真思考了一会,发现一时间还真说不明白,于是就研究了一段时间发布今天的博文! Docker vs Kubernetes&a…

Android Handler 通过线程安全的 MessageQueue 和底层唤醒机制实现跨线程通信

目录 一、MessageQueue 的线程安全实现 1. 消息队列的同步锁(synchronized) 2. 消息顺序与延时处理 二、底层唤醒机制:从 Java 到 Linux 内核 1. 消息插入后的唤醒逻辑 2. Native 层实现(基于 Linux 的 eventfd 和 epoll&am…

关于 2>/dev/null 的作用以及机理

每个进程都有三个标准文件描述符:stdin(标准输入)、stdout(标准输出)和stderr(标准错误)。默认情况下,stderr会输出到终端。使用2>可以将stderr重定向到其他地方,比如…

MySQL中的锁机制:从全局锁到行级锁

目录 1. 锁的基本概念 2. 全局锁 2.1 全局锁的定义 2.2 全局锁的类型 2.3 全局锁的使用场景 2.4 全局锁的实现方式 2.5 全局锁的优缺点 2.6 全局锁的优化 3. 表级锁 3.1 表级锁的类型 3.2 表级锁的使用场景 3.3 表级锁的优缺点 4. 意向锁(Intention Lo…

编程语言选择分析:C#、Rust、Go 与 TypeScript 编译器优化

编程语言选择分析:C#、Rust、Go 与 TypeScript 编译器优化 在讨论编程语言的选择时,特别是针对微软的 C# 和 Rust,以及谷歌的 Go 语言,以及微软试图通过 Go 来拯救 TypeScript 编译器的问题,我们可以从多个角度来分析和…

基于WebRTC的嵌入式音视频通话SDK:EasyRTC跨平台兼容性技术架构实时通信的底层实现

EasyRTC的核心架构围绕WebRTC技术构建,同时通过扩展信令服务、媒体服务器和NAT穿透机制,解决了WebRTC在实际部署中的痛点。其架构可以分为以下几个核心模块: 1)WebRTC基础层 媒体捕获与处理:通过getUserMediaAPI获取…

【Rust】包和模块管理,以及作用域等问题——Rust语言基础15

文章目录 1. 前言2. 包和 Crate3. 定义模块以及模块之间的关系4. 作用域问题4.1. 作用域问题初现4.2. 解决问题一4.3. 解决问题二4.4. super 关键字4.5. 将路径引入作用域4.6. as 关键字4.7. pub use 重导出 5. 引入的问题5.1. 引入一个外部包5.2. 嵌套路径来消除大量的 use 行…

微服务架构中的API网关:Spring Cloud与Kong/Traefik等方案对比

微服务架构中的API网关:Spring Cloud与Kong/Traefik等方案对比 一、API 网关的概念二、API 网关的主要功能2.1 统一入口与路由转发2.2 安全与权限控制2.3 流量管理与容错2.4 API 管理与聚合2.5 监控与日志2.5 协议转换与适配2.6 控制平面与配置管理 三、API 网关选型…

NewStar CTF web wp

文章目录 week1headach3会赢吗智械危机谢谢皮蛋PangBai 过家家(1) week3include meblindsql1臭皮的计算机臭皮踩踩背这照片是你吗 week4Pangbai过家家四blindsql2chocolateezcmsssezpollute隐藏的密码 weeek5pangbai过家家(5)redissqlshell臭皮吹泡泡臭皮…

Linux驱动开发-①中断②阻塞、非阻塞IO和异步通知

Linux驱动开发-①中断②阻塞、非阻塞IO和异步通知 一,中断1.中断的流程2.上半部和下半部2.1上半部2.2下半部2.2.1 tasklet2.2.2 工作队列 3.按键延时消抖中断程序 二,阻塞和非阻塞IO和异步通知1.阻塞IO1.1 常见结构11.2 常见结构2 2.非阻塞IO2.1 驱动结构…

Docker和Dify学习笔记

文章目录 1 docker学习1.1 基本命令使用1.1.1 docker ps查看当前正在运行的镜像1.1.2 docker stop停止容器1.1.3 docker compose容器编排1.1.4 docker网络[1] 进入到容器里面敲命令[2] docker network ls[3] brige网络模式下容器访问宿主机的方式 2 Dify的安装和基础使用2.1 下…

高并发库存系统是否适合使用 ORM(Hibernate / MyBatis)

在设计高并发的库存管理系统时,数据层的选择至关重要。许多企业开发中习惯使用 ORM(如 Hibernate、MyBatis)来简化数据库访问,但在高并发、高吞吐的场景下,ORM 的适用性往往成为争议焦点。本文将探讨高并发库存系统是否…

Web爬虫利器FireCrawl:全方位助力AI训练与高效数据抓取。本地部署方式

开源地址:https://github.com/mendableai/firecrawl 01、FireCrawl 项目简介 Firecrawl 是一款开源、优秀、尖端的 AI 爬虫工具,专门从事 Web 数据提取,并将其转换为 Markdown 格式或者其他结构化数据。 Firecrawl 还特别上线了一个新的功…

探秘Transformer系列之(16)--- 资源占用

探秘Transformer系列之(16)— 资源占用 文章目录 探秘Transformer系列之(16)--- 资源占用0x00 概述0x01 背景知识1.1 数据类型1.2 进制&换算数字进制存储度量换算 1.3 参数显存占用有参数的层无参数的层所需资源 1.4 计算量 0…