C# 单例模式详解

2023-11-09

定义

单例模式是比较常见的一种设计模式,目的是保证一个类只能有一个实例,而且自行实例化并向整个系统提供这个实例,避免频繁创建对象,节约内存。

单例模式的应用场景很多,

比如我们电脑的操作系统的回收站就是一个很好的单例模式应用,电脑上的文件、视频、音乐等被删除后都会进入到回收站中;还有计算机中的打印机也是采用单例模式设计的,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;Web页面的计数器也是用单例模式实现的,可以不用把每次刷新都记录到数据库中。

通过回味这些应用场景,我们对单例模式的核心思想也就有了更清晰的认识,下面就开始用代码来实现。

在写单例模式的代码之前,我们先简单了解一下两个知识点,关于类的加载顺序和static关键字。

类加载顺序

类加载(classLoader)机制一般遵从下面的加载顺序

如果类还没有被加载:

先执行父类的静态代码块和静态变量初始化,静态代码块和静态变量的执行顺序跟代码中出现的顺序有关。

执行子类的静态代码块和静态变量初始化。

执行父类的实例变量初始化

执行父类的构造函数

执行子类的实例变量初始化

执行子类的构造函数

同时,加载类的过程是线程私有的,别的线程无法进入。

如果类已经被加载:

静态代码块和静态变量不在重复执行,再创建类对象时,只执行与实例相关的变量初始化和构造方法。

static关键字

一个类中如果有成员变量或者方法被static关键字修饰,那么该成员变量或方法将独立于该类的任何对象。它不依赖类特定的实例,被类的所有实例共享,只要这个类被加载,该成员变量或方法就可以通过类名去进行访问,它的作用用一句话来描述就是,不用创建对象就可以调用方法或者变量,这简直就是为单例模式的代码实现量身打造的。

下面将列举几种单例模式的实现方式,其关键方法都是用static修饰的,并且,为了避免单例的类被频繁创建对象,我们可以用private的构造函数来确保单例类无法被外部实例化。

懒汉和饿汉

在程序编写上,一般将单例模式分为两种,分别是饿汉式和懒汉式,

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

懒汉式:在类加载时不初始化,等到第一次被使用时才初始化。

代码实现

1、饿汉式 (可用)

public class Singleton {


    private final static Singleton INSTANCE = new Singleton();
    
    private Singleton(){}


    public static Singleton getInstance(){
        return INSTANCE;
    }


}

这是比较常见的写法,在类加载的时候就完成了实例化,避免了多线程的同步问题。当然缺点也是有的,因为类加载时就实例化了,没有达到Lazy Loading (懒加载) 的效果,如果该实例没被使用,内存就浪费了。

2、普通的懒汉式 (线程不安全,不可用)

public class Singleton {


    private static Singleton instance = null;


    private Singleton() {
    }


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


}

这是懒汉式中最简单的一种写法,只有在方法第一次被访问时才会实例化,达到了懒加载的效果。但是这种写法有个致命的问题,就是多线程的安全问题。假设对象还没被实例化,然后有两个线程同时访问,那么就可能出现多次实例化的结果,所以这种写法不可采用。

3、同步方法的懒汉式 (可用)

public class Singleton {


    private static Singleton instance = null;


    private Singleton() {
    }


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


}

这种写法是对getInstance()加了锁的处理,保证了同一时刻只能有一个线程访问并获得实例,但是缺点也很明显,因为synchronized是修饰整个方法,每个线程访问都要进行同步,而其实这个方法只执行一次实例化代码就够了,每次都同步方法显然效率低下,为了改进这种写法,就有了下面的双重检查懒汉式。

4、双重检查懒汉式 (可用,推荐)

public class Singleton {


    private static volatile Singleton instance;


    private Singleton() {}


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


}

这种写法用了两个if判断,也就是Double-Check,并且同步的不是方法,而是代码块,效率较高,是对第三种写法的改进。为什么要做两次判断呢?这是为了线程安全考虑,还是那个场景,对象还没实例化,两个线程A和B同时访问静态方法并同时运行到第一个if判断语句,这时线程A先进入同步代码块中实例化对象,结束之后线程B也进入同步代码块,如果没有第二个if判断语句,那么线程B也同样会执行实例化对象的操作了。

5、静态内部类 (可用,推荐)

public class Singleton {


    private Singleton() {}


    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }


    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }


}

这是很多开发者推荐的一种写法,这种静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成对象的实例化。

同时,因为类的静态属性只会在第一次加载类的时候初始化,也就保证了SingletonInstance中的对象只会被实例化一次,并且这个过程也是线程安全的。

6、枚举 (可用、推荐)

public enum Singleton {
    INSTANCE;
}

1)线程安全问题。因为Java虚拟机在加载枚举类的时候会使用ClassLoader的方法,这个方法使用了同步代码块来保证线程安全。

2)避免反序列化破坏对象,因为枚举的反序列化并不通过反射实现。

好了,单例模式的几种写法就介绍到这了,最后简单总结一下单例模式的优缺点

单例模式的优缺点

优点

单例类只有一个实例,节省了内存资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能;

单例模式可以在系统设置全局的访问点,优化和共享数据,例如前面说的Web应用的页面计数器就可以用单例模式实现计数值的保存。

缺点

单例模式一般没有接口,扩展的话除了修改代码基本上没有其他途径。

————————————————

版权声明:本文为CSDN博主「鄙人薛某」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/yeyazhishang/article/details/90445330

技术群:添加小编微信并备注进群

小编微信:mm1552923   

公众号:dotNet编程大全      

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C# 单例模式详解 的相关文章

随机推荐

  • Swagger配置完成以后,登录账户名和密码的设置

    spring security basic path swagger ui html enabled true user name admin 账号 password 123456 密码
  • ajax长轮询tornado,数据可用时如何完成Tornado长轮询请求

    我有很长的编程背景 但对Python还不熟悉 我正在研究Tornado 以构建一个长轮询服务的原型 在 我想实现的是用户连接说example com get 1234 这是长轮询部分 1234是用户ID 目前 它只是挂起并等待内容 然后用户
  • 【从零开始的Java开发】1-2-4 Java方法

    文章目录 方法 方法分类 数组作为方法参数 方法重载 基本数据类型的传值 数组的传值 引用数据类型 可变参数列表 可变参数列表作为方法参数的重载 文档注释 方法的调试 细节与总结 方法 所谓方法 就是用来解决一类问题的代码的有序组合 是一个
  • MCU,MPU,MMU,CACHE的含义

    1 mcu和mpu CPU Central Processing Unit 中央处理器 发展出来三个分枝 一个是DSP Digital Signal Processing Processor 数字信号处理 另外两个是MCU Micro Co
  • HTML <colgroup> 标签

    实例 两个 colgroup 元素为表格中的三列规定了不同的对齐方式和样式 注意第一个 colgroup 元素横跨两列 table width 100 border 1 table
  • [138]小米笔记本怎么关闭secure boot

    关闭Secure Boot的步骤 一 关闭 快速启动 功能 1 右键 开始菜单 电源选项 进入后 点击 选择电源按钮的功能 2 进入电源选项设置后 点击 更改当前不可用的设置 再把 启用快速启动 推荐 前边的勾去掉 若没有该选择则不需要操作
  • MDK与芯片的联系

    程序执行的时候FLASH空间 code RO data 程序执行时SRAM空间 RW data ZI data 程序存储时占用空间 code RO data RW data 在目录下打开命令行窗口 按shift 鼠标右键 gt 可以将信息输
  • 区块链:Solidity值类型(Solidity 枚举Enums & 结构体Structs)

    枚举Enums 案例 pragma solidity 0 4 4 contract test enum ActionChoices GoLeft GoRight GoStraight SitStill ActionChoices choic
  • 华为OD机试 C++ 叠积木

    题目 你手里有一堆砖头 它们都有一样的宽和高 但长度不同 你想用这些砖头堆砌一堵墙 每一层墙可以只用一个砖头 也可以用两个拼接起来 但这两种情况下 每层的长度必须都是一样的 如果你想使用所有的砖头 并堆砌出尽可能多的层数 那么最多可以搭建多
  • C#(winform)调用pytorch模型

    winform调用pytorch上训练好的unet模型 项目是写一个辅助诊断系统软件 用winform写软件 调用pytorch和matlab的模型 这篇博客只包含调用pytorch模型的部分 1 c libtorch 调用模型 2 c 生
  • java使用aspose.pdf或者spire.pdf 将pdf文件转word,实测

    1 aspose pdf aspose pdf不是破解版的只能转3页 所以我弄了个破解版 aspose pdf破解版在网上都有破解方法也比较简单 这里就不说了 直接引入破解版的jar包 在这里我用的是aspose pdf 21 11 jar
  • Qt第四十五章:QComboBox 禁止滚轮

    很简单 直接反射将QComboBox的wheelEvent方法重置掉即可 self combo box QComboBox self setattr self combo box wheelEvent lambda a None
  • 车联网Apollo(阿波罗),研究carlife车机端集成及开发,(WeLink,carplay/carlife)

    Apollo 阿波罗 是携程框架部门研发的分布式配置中心 能够集中化管理应用不同环境 不同集群的配置 配置修改后能够实时推送到应用端 并且具备规范的权限 流程治理等特性 适用于微服务配置管理场景 https github com ctrip
  • C语言提取一列数据并保存

    c语言求教 txt文档只有一列数据但是有很多 需要把它提取出来 每1024个数保存在一个文件中 求大神指教 c语言
  • 什么时候需要使用引用?使用引用的好处是什么?

    作者 谢之易 链接 https www zhihu com question 34267829 answer 58414818 来源 知乎 著作权归作者所有 商业转载请联系作者获得授权 非商业转载请注明出处 记忆里 C 的设计与演化 一书提
  • 【华为机试真题 Python实现】仿 LISP 运算【2022 Q1 Q2

    题目描述 LISP 语言唯一的语法就是括号要配对 形如 OP P1 P2 括号内元素由单个空格分割 其中第一个元素 OP 为操作符 后续元素均为其参数 参数个数取决于操作符类型 注意 参数 P1 P2 也有可能是另外一个嵌套的 OP P1
  • 语音识别-初识

    ASRT https blog ailemon net 2018 08 29 asrt a chinese speech recognition system ASR Automatic Speech Recognition Paddle
  • 计算机加入域的一种方法

    重装系统后 想把机子加入域 却总是不成功 隐约记得以前老大们讲过 厚着脸皮问了 o 之后 决定记下来 省的我以后又忘了 计算机加入域 一 在网络中加入DNS地址 二 step 1 更改计算机名字 右键点击 我的电脑 打开 属性 页面 找到
  • redis-benchmark测试Redis集群性能

    基础环境配置 Redis5 三主三从cluster 1 100个并发连接 100000个请求 检测host为172 16 254 124端口为7004的redis服务器性能 redis benchmark h 172 16 254 124
  • C# 单例模式详解

    定义 单例模式是比较常见的一种设计模式 目的是保证一个类只能有一个实例 而且自行实例化并向整个系统提供这个实例 避免频繁创建对象 节约内存 单例模式的应用场景很多 比如我们电脑的操作系统的回收站就是一个很好的单例模式应用 电脑上的文件 视频