C#设计模式之Singleton模式
作者:老胡写代码 发布时间:2022-03-28 09:01:31
目录
前言
实现思路
实现方法
最简单的实现方法
如果多线程乱入?
线程安全的单例模式
Lock版本
静态构造器版本
Lazy版本
总结
前言
Singleton是二十三个设计模式中比较重要也比较经常使用的模式。但是这个模式虽然简单,实现起来也会有一些小坑,让我们一起来看看吧!
实现思路
首先我们看看这个设计模式的UML类图。
很清晰的可以看到,有三点是需要我们在实现这个模式的时候注意的地方。
私有化的构造器
全局唯一的静态实例
能够返回全局唯一静态实例的静态方法
其中,私有化构造器是防止外部用户创建新的实例而静态方法用于返回全局唯一的静态实例供用户使用。原理清楚了,接下来我们看看一些典型的实现方式和其中的暗坑。
实现方法
最简单的实现方法
最简单的实现方法自然就是按照UML类图直接写一个类,我们看看代码。
class Program
{
static void Main(string[] args)
{
var single1 = Singleton.Instance;
var single2 = Singleton.Instance;
Console.WriteLine(object.ReferenceEquals(single1, single2));
Console.ReadLine();
}
}
class Singleton
{
private static Singleton _Instance = null;
private Singleton()
{
Console.WriteLine("Created");
}
public static Singleton Instance
{
get
{
if (_Instance == null)
{
_Instance = new Singleton();
}
return _Instance;
}
}
public void DumbMethod()
{
}
}
这段代码忠实的实现了UML类图里面的一切,查看输出结果,
证实了Singleton确实起了作用,多次调用仅仅产生了一个实例,似乎这么写就可以实现这个模式了。但是,真的会那么简单吗?
如果多线程乱入?
现在我们给刚刚的例子加点调料,假设多个对实例的调用,并不是简单的,彬彬有礼的顺序关系,二是以多线程的方式调用,那么刚刚那种实现方法,还能从容应对吗?让我们试试。把Main函数里面的调用改成这样。
static void Main(string[] args)
{
int TOTAL = 10000;
Task[] tasks = new Task[TOTAL];
for (int i = 0; i < TOTAL; i++)
{
tasks[i] = Task.Factory.StartNew(() =>
{
Singleton.Instance.DumbMethod();
});
}
Task.WaitAll(tasks);
Console.ReadLine();
}
通过Factory创造出1万个Task,几乎同时去请求这个单例,看看输出。
咦,我们刚刚写的Singleton模式失效了,这个类被创造了5次(这段代码运行多次,这个数字不一定相同),一定是多线程搞的鬼,我们刚刚写的代码没有办法应对多线程,换句话说,是非线程安全的(thread-safe),那有没有办法来攻克这个难关呢?
线程安全的单例模式
Lock版本
提到线程安全,很多同学第一反应就是用lock,不错,lock是个可行的办法,让我们试试。添加一个引用类型的对象作为lock对象,修改代码如下(什么?你问我为什必须是引用类型的对象而不能是值类型的对象?因为lock的时候,如果对象是值类型,那么lock仅仅锁住了它的一个副本,另外一个线程可以畅通无阻的再次lock,这样lock就失去了阻塞线程的意义)
private static object _SyncObj = new object();
public static Singleton Instance
{
get
{
lock (_SyncObj)
{
if (_Instance == null)
{
_Instance = new Singleton();
}
return _Instance;
}
}
}
运行一下,输出
只有一个实例创建,证明Lock起作用了,这个模式可行!不过有些不喜欢用Lock的同学可能要问,还有没有其他办法呢?答案是有的。
静态构造器版本
回想一下,C#中的类静态构造器,只会在这个类第一次被使用的时候调用一次,天然的线程安全,那我们试试不用Lock使用类静态构造器?修改Singleton类如下:
class Singleton
{
private static Singleton _Instance = null;
private Singleton()
{
Console.WriteLine("Created");
}
static Singleton()
{
_Instance = new Singleton();
}
//private static object _SyncObj = new object();
public static Singleton Instance
{
get { return _Instance; }
}
public void DumbMethod()
{
}
}
去掉了Lock,添加了一个类静态构造器,试一试。
完美!对于不喜欢用Lock(在这个例子中,实例只会创建一次但是之后的所有线程都要先排队Lock再进入Critical code进行检查,效率比较低下)的同学,类静态构造器提供了一种很好的选择。
不过俗话说,人心苦不足 , 我们总是追求卓越。这个版本比Lock版本似乎更好一点,那还有没有更好的版本呢?有的。
Lazy版本
从net 4.0开始,C#开始支持延迟初始化,通过Lazy关键字,我们可以声明某个对象为仅仅当第一次使用的时候,再初始化,如果一直没有调用,那就不初始化,省去了一部分不必要的开销,提升了效率。如果你不熟悉Lazy或者想更多了解它,请参考。我们今天关注的重点在于,Lazy也是天生线程安全的,所以我们尝试用它来实现Singleton模式?修改代码如下:
class Singleton
{
private static Lazy<Singleton> _Instance = new Lazy<Singleton>(() => new Singleton());
private Singleton()
{
Console.WriteLine("Created");
}
public static Singleton Instance
{
get
{
return _Instance.Value;
}
}
public void DumbMethod()
{
}
}
输出结果中可以看到,我们达到了想要的效果:
在上面的代码中,私有变量_Instance现在是被声明为延迟初始化,这样不但天然实现了线程安全,同时在没有调用Instance静态方法的时候(也即没有调用_Instance.Value),初始化不会发生,这样就提高了效率。
总结
Singleton模式很常见,实现起来也很简单,只是要小心线程安全。以上三种方法都可以实现线程安全的Singleton模式。如果net 版本在4.0之上,建议使用Lazy版本,毕竟对比Lock版本,Lazy版本可以免去实现手动Lock之苦,对比Static版本,又有延迟初始化的性能优势,何乐而不为呢?
来源:https://www.cnblogs.com/deatharthas/p/13047660.html


猜你喜欢
- 前言gradle的定义(来自 * )Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化建构工具。它使用一
- 效果图一、绘制圆环圆环故名思意,第一个首先绘制是圆环1:圆环绘制函数圆环APIpublic void drawArc (RectF oval
- 设计模式要进行共性与可变性的分析,对共性进行抽象,同时对可变性进行封装,没有完美的设计模式,作为一名开发者要懂得取舍,触类旁通,开发出高内聚
- 首先给大家声明一点:需要 jdk 7 , tomcat需要支持websocket的版本 1.InitServlet &n
- 使用示例:package cn.hackcoder.beautyreader.db;import android.content.Conte
- 快捷键是很多软件的常用功能,本文实例讲解了三种方法来实现C# button快捷键,如Alt + *(按钮快捷键),Ctrl+*及其他组合键等
- 1、实现这里主要用的是反射的方法。用户要传入方法名和方法参数,我们就需要先写函数返回这些信息,最后再包装一下返回给用户。获取某一程序集下所有
- 定义:定义对象间一种一对多的依赖关系,使得当每一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。类型:行为类模式类图: 
- 在分支较多的时候,switch的效率比if高,在反汇编中我们即可看到效率高的原因一、switch语句1、在正向编码时,switch语句可以看
- Spring spring-context-indexer依赖<dependencies> <d
- 需要修改Main方法第一行代码的路径为你的books.xml文件绝对路径或相对路径。代码演示了XPath各种语法的使用情况books.xml
- 零、Gallery的使用回顾我们有时候在iPhone手机上或者Windows上面看到动态的图片,可以通过鼠标或者手指触摸来移动它,产生动态的
- 根据用户系统时区动态展示时间当我们使用SpringBoot+Mysql开发系统时,总是统一设置UTC+8时区,这样用户在任何地区访问系统,展
- 前言之前用springboot的时候,只知道捕获异常使用try{}catch,一个接口一个try{}catch,这也是大多数开发人员异常处理
- 首先:看问题图,如下可以激活ide的网址很多,估计是个团队或者个人,直接买了全部产品的一年的有效期。而且还是会一直更新下去的。因为,后来我自
- 本文汇总了几个WinForm中常见的实用技巧,对于C#程序开发有着很好的参考借鉴价值。具体分析如下:一、屏蔽窗体右上角关闭按钮1.重写OnC
- 前言今天记录一个前段时间遇到的一个小问题的解决方法, 跨域!!!相信跨域这个问题, 做开发的或多或少都遇到过, 而且已经有很多博主已经分享了
- 本文实例讲述了Android编程实现自动检测版本及自动升级的方法。分享给大家供大家参考,具体如下:步骤:1.检测当前版本的信息Android
- 创建类第一步新建一个java类QSV,构造函数传入需要解析的文件名称。public class QSV {private RandomAcc
- 随着Android设备增多,不少网站都开始设备Android设备,而Android主流设备类型以手机和平板为主。网站在适配时通过User A