简介

单例指的是只能存在一个实例的类(在C#中,更准确的说法是在每个AppDomain之中只能存在一个实例的类,它是软件工程中使用最多的几种模式之一。在第一个使用者创建了这个类的实例之后,其后需要使用这个类的就只能使用之前创建的实例,无法再创建一个新的实例。通常情况下,单例会在第一次被使用时创建。本文会对C#中几种单例的实现方式进行介绍,并分析它们之间的线程安全性和性能差异。
单例的实现方式有很多种,但从最简单的实现(非延迟加载,非线程安全,效率低下),到可延迟加载,线程安全,且高效的实现,它们都有一些基本的共同点:
几种实现
一非线程安全
//Bad code! Do not use!
public sealed class Singleton
{
private static Singleton instance = null;
private Singleton()
{
}
public static Singleton instance
{
get
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
这种方法不是线程安全的,会存在两个线程同时执行if (instance == null)并且创建两个不同的instance,后创建的会替换掉新创建的,导致之前拿到的reference为空。
二简单的线程安全实现
public sealed class Singleton
{
private static Singleton instance = null;
private static readonly object padlock = new object();
Singleton()
{
}
public static Singleton Instance
{
get
{
lock (padlock)
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
}
相比较于实现一,这个版本加上了一个对instance的锁,在调用instance之前要先对padlock上锁,这样就避免了实现一中的线程冲突,该实现自始至终只会创建一个instance了。但是,由于每次调用Instance都会使用到锁,而调用锁的开销较大,这个实现会有一定的性能损失。
注意这里我们使用的是新建一个private的object实例padlock来实现锁操作,而不是直接对Singleton进行上锁。直接对类型上锁会出现潜在的风险,因为这个类型是public的,所以理论上它会在任何code里调用,直接对它上锁会导致性能问题,甚至会出现死锁情况。
Note: C#中,同一个线程是可以对一个object进行多次上锁的,但是不同线程之间如果同时上锁,就可能会出现线程等待,或者严重的会出现死锁情况。因此,我们在使用lock时,尽量选择类中的私有变量上锁,这样可以避免上述情况发生。
三双重验证的线程安全实现
public sealed calss Singleton
{
private static Singleton instance = null;
private static readonly object padlock = new object();
Singleton()
{
}
public static Singleton Instance
{
get
{
if (instance == null)
{
lock (padlock)
{
if (instance == null)
{
instance = new Singleton();
}
}
}
return instance;
}
}
}
在保证线程安全的同时,这个实现还避免了每次调用Instance都进行lock操作,这会节约一定的时间。
但是,这种实现也有它的缺点:
1无法在Java中工作。(具体原因可以见原文,这边没怎么理解)
2程序员在自己实现时很容易出错。如果对这个模式的代码进行自己的修改,要倍加小心,因为double check的逻辑较为复杂,很容易出现思考不周而出错的情况。
四不用锁的线程安全实现
public sealed class Singleton
{
//在Singleton第一次被调用时会执行instance的初始化
private static readonly Singleton instance = new Singleton();
//Explicit static consturctor to tell C# compiler
//not to mark type as beforefieldinit
static Singleton()
{
}
private Singleton()
{
}
public static Singleton Instance
{
get
{
return instance;
}
}
}
这个实现很简单,并没有用到锁,但是它仍然是线程安全的。这里使用了一个static,readonly的Singleton实例,它会在Singleton第一次被调用的时候新建一个instance,这里新建时候的线程安全保障是由.NET直接控制的,我们可以认为它是一个原子操作,并且在一个AppDomaing中它只会被创建一次。
这种实现也有一些缺点:
1instance被创建的时机不明,任何对Singleton的调用都会提前创建instance
2static构造函数的循环调用。如有A,B两个类,A的静态构造函数中调用了B,而B的静态构造函数中又调用了A,这两个就会形成一个循环调用,严重的会导致程序崩溃。
3我们需要手动添加Singleton的静态构造函数来确保Singleton类型不会被自动加上beforefieldinit这个Attribute,以此来确保instance会在第一次调用Singleton时才被创建。
4readonly的属性无法在运行时改变,如果我们需要在程序运行时dispose这个instance再重新创建一个新的instance,这种实现方法就无法满足。
五完全延迟加载实现(fully lazy instantiation)
public sealed class Singleton
{
private Singleton()
{
}
public static Singleton Instance
{
get
{
return Nested.instance;
}
}
private class Nested
{
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Nested()
{
}
internal static readonly Singleton instance = new Singleton();
}
}
实现五是实现四的包装。它确保了instance只会在Instance的get方法里面调用,且只会在第一次调用前初始化。它是实现四的确保延迟加载的版本。
六 使用.NET4的Lazy<T>类型
public sealed class Singleton
{
private static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton());
public static Singleton Instance
{
get
{
return lazy.Value;
}
}
private Singleton()
{
}
}
.NET4或以上的版本支持Lazy<T>来实现延迟加载,它用最简洁的代码保证了单例的线程安全和延迟加载特性。
性能差异
之前的实现中,我们都在强调代码的线程安全性和延迟加载。然而在实际使用中,如果你的单例类的初始化不是一个很耗时的操作或者初始化顺序不会导致bug,延迟初始化是一个可有可无的特性,因为初始化所占用的时间是可以忽略不计的。
在实际使用场景中,如果你的单例实例会被频繁得调用(如在一个循环中),那么为了保证线程安全而带来的性能消耗是更值得关注的地方。
为了比较这几种实现的性能,我做了一个小测试,循环拿这些实现中的单例9亿次,每次调用instance的方法执行一个count++操作,每隔一百万输出一次,运行环境是MBP上的Visual Studio for Mac。结果如下:
| 线程安全性 | 延迟加载 | 测试运行时间(ms) | |
|---|---|---|---|
| 实现一 | 否 | 是 | 15532 |
| 实现二 | 是 | 是 | 45803 |
| 实现三 | 是 | 是 | 15953 |
| 实现四 | 是 | 不完全 | 14572 |
| 实现五 | 是 | 是 | 14295 |
| 实现六 | 是 | 是 | 22875 |
测试方法并不严谨,但是仍然可以看出,方法二由于每次都需要调用lock,是最耗时的,几乎是其他几个的三倍。排第二的则是使用.NET Lazy类型的实现,比其他多了二分之一左右。其余的四个,则没有明显区别。
总结
总体来说,上面说的多种单例实现方式在现今的计算机性能下差距都不大,除非你需要特别大并发量的调用instance,才会需要去考虑锁的性能问题。
对于一般的开发者来说,使用方法二或者方法六来实现单例已经是足够好的了,方法四和五则需要对C#运行流程有一个较好的认识,并且实现时需要掌握一定技巧,并且他们节省的时间仍然是有限的。
引用
本文大部分是翻译自Implementing the Singleton Pattern in C#,加上了一部分自己的理解。这是我搜索static readonly field initializer vs static constructor initialization时看到的,在这里对两位作者表示感谢。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# C#单例模式
# C#实现单例模式
# c# 单例模式的实现
# C#实现单例模式的几种方法总结
# c# 单例模式的实现方法
# c#设计模式之单例模式的实现方式
# c#单例模式(Singleton)的6种实现
# C#窗口实现单例模式的方法
# C#实现单例模式的多种方式
# 会在
# 加载
# 几种
# 死锁
# 它是
# 自己的
# 来实现
# 也有
# 上了
# 有一个
# 很容易
# 只会
# 仍然是
# 创建一个
# 新建一个
# 类中
# 的是
# 是一个
# 几个
# 就会
相关文章:
手机怎么制作网站教程步骤,手机怎么做自己的网页链接?
网站制作企业,网站的banner和导航栏是指什么?
广州网站设计制作一条龙,广州巨网网络科技有限公司是干什么的?
网站建设设计制作营销公司南阳,如何策划设计和建设网站?
如何通过wdcp面板快速创建网站?
建站中国必看指南:CMS建站系统+手机网站搭建核心技巧解析
大学网站设计制作软件有哪些,如何将网站制作成自己app?
定制建站方案优化指南:企业官网开发与建站费用解析
如何在宝塔面板中修改默认建站目录?
武清网站制作公司,天津武清个人营业执照注销查询系统网站?
广州顶尖建站服务:企业官网建设与SEO优化一体化方案
整人网站在线制作软件,整蛊网站退不出去必须要打我是白痴才能出去?
专业网站建设制作报价,网页设计制作要考什么证?
在线教育网站制作平台,山西立德教育官网?
已有域名和空间,如何快速搭建网站?
建站之星五站合一营销型网站搭建攻略,流量入口全覆盖优化指南
网页设计网站制作软件,microsoft office哪个可以创建网页?
如何做静态网页,sublimetext3.0制作静态网页?
专业网站制作服务公司,有哪些网站可以免费发布招聘信息?
建站之星Pro快速搭建教程:模板选择与功能配置指南
Android自定义控件实现温度旋转按钮效果
阿里云网站搭建费用解析:服务器价格与建站成本优化指南
大连网站设计制作招聘信息,大连投诉网站有哪些?
如何获取PHP WAP自助建站系统源码?
如何在阿里云购买域名并搭建网站?
威客平台建站流程解析:高效搭建教程与设计优化方案
如何在万网ECS上快速搭建专属网站?
如何登录建站主机?访问步骤全解析
常州自助建站费用包含哪些项目?
音响网站制作视频教程,隆霸音响官方网站?
香港服务器选型指南:免备案配置与高效建站方案解析
建站之星如何配置系统实现高效建站?
如何在IIS中新建站点并配置端口与物理路径?
如何在橙子建站中快速调整背景颜色?
如何打造高效商业网站?建站目的决定转化率
如何用美橙互联一键搭建多站合一网站?
招贴海报怎么做,什么是海报招贴?
小米网站链接制作教程,请问miui新增网页链接调用服务有什么用啊?
如何用y主机助手快速搭建网站?
建站上传速度慢?如何优化加速网站加载效率?
建站之星代理如何获取技术支持?
网站设计制作书签怎么做,怎样将网页添加到书签/主页书签/桌面?
Swift中swift中的switch 语句
网站制作免费,什么网站能看正片电影?
网站制作哪家好,cc、.co、.cm哪个域名更适合做网站?
建站10G流量真的够用吗?如何应对访问高峰?
邀请函制作网站有哪些,有没有做年会邀请函的网站啊?在线制作,模板很多的那种?
Swift中switch语句区间和元组模式匹配
如何在局域网内绑定自建网站域名?
如何用搬瓦工VPS快速搭建个人网站?
*请认真填写需求信息,我们会在24小时内与您取得联系。