c#中单例模式详解
基础介绍:
确保一个类只有一个实例,并提供一个全局访问点。
适用于需要频繁实例化然后销毁的对象,创建对象消耗资源过多,但又经常用到的对象,频繁访问数据库或文件的对象。
其本质就是保证在整个应用程序的生命周期中,任何一个时刻,单例类的实例都只存在一个。
- 特性和功能:确保一个类只有一个实例,并提供一个全局访问点。
- 使用环境:当类只需要一个实例,且易于访问,且实例应在整个应用程序中共享时。
- 注意事项:需要注意线程安全问题。
- 优点:可以确保一个类只有一个实例,减少了内存开销。
- 缺点:没有接口,扩展困难。
应用场景:
单例模式通常适用于在整个应用程序中只需要一个实例化对象的场景,以确保资源的高效利用和应用程序的稳定性。(共享资源)
资源共享的情况下,避免由于资源操作时导致的性能或损耗等。
控制资源的情况下,方便资源之间的互相通信。如线程池等。
- 日志系统:在应用程序中,通常只需要一个日志系统,以避免在多个地方创建多个日志对象。这一般是由于共享的日志文件一直处于打开状态,所以只能有一个实例去操作,否则内容不好追加也有可能造成资源占用加剧资源消耗。
- 数据库连接池:在应用程序中,数据库连接池是一个非常重要的资源,单例模式可以确保在应用程序中只有一个数据库连接池实例,避免资源浪费。主要是节省打开或者关闭数据库连接所引起的效率损耗,因为何用单例模式来维护,就可以大大降低这种损耗。
- 配置文件管理器:在应用程序中,通常只需要一个配置文件管理器来管理应用程序的配置文件,单例模式可以确保在整个应用程序中只有一个配置文件管理器实例。这个是由于配置文件是共享的资源。
- 缓存系统:在应用程序中,缓存系统是一个重要的组件,单例模式可以确保在整个应用程序中只有一个缓存实例,以提高应用程序的性能。
- 网站在线人数统计:其实就是全局计数器,也就是说所有用户在相同的时刻获取到的在线人数数量都是一致的。
- GUI组件:在图形用户界面(GUI)开发中,单例模式可以确保在整个应用程序中只有一个GUI组件实例,以确保用户界面的一致性和稳定性。
创建方式:
饿汉式:类加载就会导致该单实例对象被创建。(静态变量方式、静态代码块方式)
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建。(线程不安全型、线程安全型、双重检查锁)
懒汉式---非线程安全型
- 1 public class Singleton
- 2 {
- 3 //定义一个私有的静态全局变量来保存该类的唯一实例
- 4 private static Singleton singleton;
- 5
- 6 /// <summary>
- 7 /// 构造函数
- 8 /// </summary>
- 9 private Singleton()
- 10 {
- 11 //必须是私有的构造函数,这样就可以保证该类无法通过new来创建该类的实例。
- 12 //想要使用该类只能通过唯一访问点GetInstance()。
- 13 }
- 14
- 15 /// <summary>
- 16 /// 全局访问点
- 17 /// 设置为静态方法则可在外边无需创建该类的实例就可调用该方法
- 18 /// </summary>
- 19 /// <returns></returns>
- 20 public static Singleton GetInstance()
- 21 {
- 22 if (singleton == null)
- 23 {
- 24 singleton = new Singleton();
- 25 }
- 26 return singleton;
- 27 }
- 28 }
上面的代码中,由于构造函数被设置为 private 了,无法再在 Singleton 类的外部使用 new 来实例化一个实例,只能通过访问 GetInstance()来访问 Singleton 类。
GetInstance()通过如下方式保证该 Singleton 只存在一个实例:
首先这个 Singleton 类会在在第一次调用 GetInstance()时创建一个实例(第24行),并将这个实例的引用封装在自身类中的静态全局变量singleton(第4行),
然后以后调用 GetInstance()时就会判断这个 Singleton 是否存在一个实例了(第22行),如果存在,则不会再创建实例。
这样就实现了懒加载的效果。但是,如果是多线程环境,会出现线程安全问题。
比如多个线程同时执行GetInstance()方法时都走到了第22行,这个时候一个线程进入 if 判断语句后但还没有实例化 Singleton 时,第二个线程到达,此时 singleton 还是为 null。
如此会造成多个线程都会进入 if 执行代码块中即都会执行第24行,这样的话,就会创建多个实例,违背了单里模式,因此引出了实例2线程安全型。
- 1 public class Singleton
懒汉式---线程安全型
- 1 public class Singleton
- 2 {
- 3 //定义一个私有的静态全局变量来保存该类的唯一实例
- 4 private static Singleton singleton;
- 5
- 6 //线程锁
- 7 private static readonly object _Object = new object();
- 8
- 9 /// <summary>
- 10 /// 构造函数
- 11 /// </summary>
- 12 private Singleton()
- 13 {
- 14 //必须是私有的构造函数,这样就可以保证该类无法通过new来创建该类的实例。
- 15 //想要使用该类只能通过唯一访问点GetInstance()。
- 16 }
- 17
- 18 /// <summary>
- 19 /// 全局访问点
- 20 /// 设置为静态方法则可在外边无需创建该类的实例就可调用该方法
- 21 /// </summary>
- 22 /// <returns></returns>
- 23 public static Singleton GetInstance()
- 24 {
- 25 lock (_Object)
- 26 {
- 27 if (singleton == null)
- 28 {
- 29 singleton = new Singleton();
- 30 }
- 31 }
- 32 return singleton;
- 33 }
- 34 }
相比实例1中可以看到在类中有定义了一个静态的只读对象 _Object(第7行),该对象主要是提供给lock 关键字使用。
lock关键字参数必须为基于引用类型的对象,该对象用来定义锁的范围。
当多个线程同时进入GetInstance()方法时,由于存在锁机制,当一个线程进入lock代码块时,其余线程会在lock语句的外部等待。
当第一个线程执行完第29行创建对象实例后,便会退出锁定区域,这个时候singleton变量已经不为null了。
所以余下线程再次进入lock代码块时,由于第27行的原因则不会再次创建对象的实例。
但这里就涉及一个性能问题了,每一次有线程进入 GetInstance()时,均会执行锁定操作来实现线程同步,这是非常耗费性能的。
解决这个问题也很简单,进行双重检查锁定判断即实例3。
- 1 public class Singleton
懒汉式---双重检查锁
- 1 public class Singleton
- 2 {
- 3 //定义一个私有的静态全局变量来保存该类的唯一实例
- 4 private static Singleton singleton;
- 5
- 6 //线程锁
- 7 private static readonly object _Object = new object();
- 8
- 9 /// <summary>
- 10 /// 构造函数
- 11 /// </summary>
- 12 private Singleton()
- 13 {
- 14 //必须是私有的构造函数,这样就可以保证该类无法通过new来创建该类的实例。
- 15 //想要使用该类只能通过唯一访问点GetInstance()。
- 16 }
- 17
- 18 /// <summary>
- 19 /// 全局访问点
- 20 /// 设置为静态方法则可在外边无需创建该类的实例就可调用该方法
- 21 /// </summary>
- 22 /// <returns></returns>
- 23 public static Singleton GetInstance()
- 24 {
- 25 if (singleton == null)//第一重
- 26 {
- 27 lock (_Object)
- 28 {
- 29 if (singleton == null)//第二重
- 30 {
- 31 singleton = new Singleton();
- 32 }
- 33 }
- 34 }
- 35 return singleton;
- 36 }
- 37 }
相比实例2来看,只是增加了第25行。
在多线程中,当第一个线程创建完对象的实例后,singleton变量已经不为null了。之后再访问GetInstance()方法时,将不会再进行lock等待。
如果没有这行的情况下,每次多线程同时进入GetInstance()方法时,多余的线程都会进入lock进行等待。这是非常耗费性能的。
相比调用GetInstance()方法来作为全局访问点还有另外一种写法:
- 1 public class Singleton
- 2 {
- 3 private static Singleton instance;
- 4
- 5 private Singleton() { }
- 6
- 7 public static Singleton Instance
- 8 {
- 9 get
- 10 {
- 11 if (instance == null)
- 12 {
- 13 instance = new Singleton();
- 14 }
- 15 return instance;
- 16 }
- 17 }
- 18 }
前三个实例在客户端调用:Singleton singletonOne = Singleton.GetInstance();
后一种则可以直接:Singleton.Instance进行使用。
- 1 public class Singleton
饿汉式
- 1 public sealed class Singleton
- 2 {
- 3 //定义一个私有静态的只读的全局变量
- 4 private static readonly Singleton singleton = new Singleton();
- 5
- 6 /// <summary>
- 7 /// 构造函数
- 8 /// </summary>
- 9 private Singleton()
- 10 {
- 11 //必须是私有的构造函数,这样就可以保证该类无法通过new来创建该类的实例。
- 12 //想要使用该类只能通过唯一访问点GetInstance()。
- 13 }
- 14
- 15 /// <summary>
- 16 /// 全局访问点
- 17 /// 设置为静态方法则可在外边无需创建该类的实例就可调用该方法
- 18 /// </summary>
- 19 /// <returns></returns>
- 20 public static Singleton GetInstance()
- 21 {
- 22 return singleton;
- 23 }
- 24 }
在c#中使用静态初始化时无需显示地编写线程安全代码,C# 与 CLR 会自动解决前面提到的懒汉式单例类时出现的多线程同步问题。
当整个类被加载的时候,就会自行初始化 singleton 这个静态只读变量。
而非在第一次调用 GetInstance()时再来实例化单例类的唯一实例,所以这就是一种饿汉式的单例类。
- 1 public sealed class Singleton
总结:
Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。
(1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如日志文件,应用配置。
(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。
c#中单例模式详解的更多相关文章
- 9种Java单例模式详解(推荐)
单例模式的特点 一个类只允许产生一个实例化对象. 单例类构造方法私有化,不允许外部创建对象. 单例类向外提供静态方法,调用方法返回内部创建的实例化对象. 懒汉式(线程不安全) 其主要表现在单例类在外 ...
- winxp计算机管理中服务详解
winxp计算机管理中服务详解01 http://blog.sina.com.cn/s/blog_60f923b50100efy9.html http://blog.sina.com.cn/s/blo ...
- cocos2dx常见的46中+22中动作详解
cocos2dx常见的46中+22中动作详解 分类: iOS2013-10-16 00:44 1429人阅读 评论(0) 收藏 举报 bool HelloWorld::init(){ ///// ...
- Android中Context详解 ---- 你所不知道的Context
转自:http://blog.csdn.net/qinjuning/article/details/7310620Android中Context详解 ---- 你所不知道的Context 大家好, ...
- iOS中-Qutarz2D详解及使用
在iOS中Qutarz2D 详解及使用 (一)初识 介绍 Quartz 2D是二维绘图引擎. 能完成的工作有: 绘制图形 : 线条\三角形\矩形\圆\弧等 绘制文字 绘制\生成图片(图像) 读取\生成 ...
- 【转】declare-styleable的使用(自定义控件) 以及declare-styleable中format详解
原文网址:http://www.cnblogs.com/622698abc/p/3348692.html declare-styleable是给自定义控件添加自定义属性用的 1.首先,先写attrs. ...
- Python中dict详解
from:http://www.cnblogs.com/yangyongzhi/archive/2012/09/17/2688326.html Python中dict详解 python3.0以上,pr ...
- 【转】 java中HashMap详解
原文网址:http://blog.csdn.net/caihaijiang/article/details/6280251 java中HashMap详解 HashMap 和 HashSet 是 Jav ...
- java中HashMap详解(转)
java中HashMap详解 博客分类: JavaSE Java算法JDK编程生活 HashMap 和 HashSet 是 Java Collection Framework 的两个重要成 ...
- java集合(2)- java中HashMap详解
java中HashMap详解 基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了非同步和允许使用 null 之外,HashMap 类与 H ...
随机推荐
- 2023-07-25:你驾驶出租车行驶在一条有 n 个地点的路上 这 n 个地点从近到远编号为 1 到 n ,你想要从 1 开到 n 通过接乘客订单盈利。你只能沿着编号递增的方向前进,不能改变方向 乘
2023-07-25:你驾驶出租车行驶在一条有 n 个地点的路上 这 n 个地点从近到远编号为 1 到 n ,你想要从 1 开到 n 通过接乘客订单盈利.你只能沿着编号递增的方向前进,不能改变方向 乘 ...
- 与AI对话 -- 20230215 -- linux 启动参数与控制台
linux 启动参数 console=ttyS0,115200n8 console=tty0 说明 console=ttyS0,115200n8:指定系统使用 ttyS0(ttyS1.ttyS2 以此 ...
- 如何编写难以维护的React代码?耦合组件
如何编写难以维护的React代码?耦合组件 在许多项目中,我们经常会遇到一些难以维护的React代码.其中一种常见的情况是:子组件直接操作父组件方法,从而导致父子组件深度耦合.这样的实现让子组件过于依 ...
- IDEA:使用Test注解,控制台无法输入
解决方案 步骤一: 点击help ===> Edit Custom VM Options... 步骤二: 添加文件末尾添加如下内容 -Deditable.java.test.console=tr ...
- Abstract Factory 抽象工厂模式简介与 C# 示例【创建型1】【设计模式来了_1】
〇.简介 1.什么是抽象工厂模式? 一句话解释: 提供一个接口,以创建一系列相关或相互依赖的抽象对象,而无需指定它们具体的类.(将一系列抽象类装进接口,一次接口实现,就必须实例化这一系列抽象类) ...
- [nginx]lua读取请求体
前言 nginx默认不读取请求体的数据,但可以通过$request_body内置变量来获取.$request_body存在内存中,如果它的字节大小超过nginx配置的client_body_buffe ...
- 应用程序通过 Envoy 代理和 Jaeger 进行分布式追踪(一)
Istio 支持通过 Envoy 代理进行分布式追踪,代理自动为其应用程序生成追踪 span,只需要应用程序转发适当的请求上下文即可.Istio 支持很多追踪系统,包括 Zipkin, Jaeger, ...
- 知识图谱(Knowledge Graph)- Neo4j 5.10.0 Docker 安装
知识图谱(Knowledge Graph)- Neo4j 5.10.0 Docker 安装 知识图谱(Knowledge Graph)- Neo4j 5.10.0 CentOS 安装 https:// ...
- 开机自动打开termux以及启动termux的服务
ps:因为我们的服务是安装在平板上面的termux,客户不想维护麻烦,如果平板重启之后还需要手动启动ternux,还要开启命令启动服务,这样比较麻烦,所以研究如下操作 1.安装macroDroid 直 ...
- 2023-08-24:请用go语言编写。给定一个长度为n的数组arr, 现在你有一次机会, 将其中连续的K个数全修改成任意一个值, 请你计算如何修改可以使修改后的数 列的最长不下降子序列最长。 请输出
2023-08-24:请用go语言编写.给定一个长度为n的数组arr, 现在你有一次机会, 将其中连续的K个数全修改成任意一个值, 请你计算如何修改可以使修改后的数 列的最长不下降子序列最长. 请输出 ...