1.0 c#设计模式 --单例模式
前言
先简单说一下我前期(不理解的时候),记住单例模式的诀窍,是之前公开课上老师讲的。单例模式,双 if+lock
为什么存在单例模式?
很多时候,一个对象我们需要使用多次。但是每次使用都要新建一次。如果实例化的时候非常耗时,那么整体的系统就会相当缓慢。
如下代码,普通类型:
///
/// 学习单例模式案例
///
public class DemoSingleton
{
public DemoSingleton()
{
//模拟耗时较长的情况
Thread.Sleep(1000);
Console.WriteLine($"我诞生啦!在{Thread.CurrentThread.ManagedThreadId}中创建");
}
public void Test()
{
Console.WriteLine("DemoSingleton Test 输出");
}
}
class Program
{
static void Main(string[] args)
{
//调用示例
for(var i=1;i<10;i++)
{
var demo = new DemoSingleton();
demo.Test();
}
Console.ReadKey();
}
}
执行后的结果如下图:

根据上述执行结果我们可以看出,每次都要实例化一次类型(在我们的模拟中,实例化耗时是比较长的),所以比较浪费资源。我们应该如何改进这种方式呢?
比较简单的就是,我们将实例化从for循环中拿出来,这样就可以实例化一次,避免了浪费时间的问题。但是,如果调用存在于多个方法中呢?
所以,我们可以将实例化放到类中实现,代码如下:
///
/// 学习单例模式案例
///
public class DemoSingleton
{
private static DemoSingleton demo;
private DemoSingleton()
{
//模拟耗时较长的情况
Thread.Sleep(1000);
Console.WriteLine($"我诞生啦!在{Thread.CurrentThread.ManagedThreadId}中创建");
}
public static DemoSingleton CreateInstance()
{
if (demo == null)
{
demo = new DemoSingleton();
}
return demo;
}
public void Test()
{
Console.WriteLine("DemoSingleton Test 输出");
}
}
static void Main(string[] args)
{
//调用示例
for (var i = 1; i < 10; i++)
{
var demo = DemoSingleton.CreateInstance();
demo.Test();
}
Console.ReadKey();
}
如上所示,将实例化的过程放在类中,这样不论在哪里调用,都会使用的同一个类(为了保证外部不能实例化,所以构造函数使用了私有的)。具体执行效果请看下图:

如上图所示,结果还是与我们想要的是一致的。但是,在程序开发过程中,通常我们会使用多线程等来处理问题,构建系统。那么调用方法就会被改成如下情况:
static void Main(string[] args)
{
//调用示例
for (var i = 1; i < 10; i++)
{
new Action(() => {
var demo = DemoSingleton.CreateInstance();
demo.Test();
}).BeginInvoke(null,null);
}
Console.ReadKey();
}
调用结果如下图:

完全不是我们想要的,但是问题出在哪里呢?
由于我们使用的是异步方法,所以一次进入IF条件的有很多。所以,解决上面的问题就是解决如何限制一次进入IF条件的有单个。比较简单的方式,就是加一个锁,防止一次进入条件的有多个。具体代码如下:
///
/// 学习单例模式案例
///
public class DemoSingleton
{
private static DemoSingleton demo;
private static readonly object _lock=new object();
private DemoSingleton()
{
//模拟耗时较长的情况
Thread.Sleep(1000);
Console.WriteLine($"我诞生啦!在{Thread.CurrentThread.ManagedThreadId}中创建");
}
public static DemoSingleton CreateInstance()
{
if (demo == null)
{
lock (_lock)
{
demo = new DemoSingleton();
}
}
return demo;
}
public void Test()
{
Console.WriteLine("DemoSingleton Test 输出");
}
}
static void Main(string[] args)
{
//调用示例
for (var i = 1; i < 10; i++)
{
new Action(() => {
var demo = DemoSingleton.CreateInstance();
demo.Test();
}).BeginInvoke(null,null);
}
Console.ReadKey();
}
执行效果如下图:

如上图,不是我们想要的。这时候,有的人就说了,你的目的是让进入if条件的只有一个,应该把lock放到if的外层,那我们来修改一下构造函数:
public static DemoSingleton CreateInstance()
{
lock(_lock)
{
if (demo == null)
{
demo = new DemoSingleton();
}
}
return demo;
}
执行结果结果如下:

如上图所示,执行结果是我们想要的了。此刻,不断进行优化的我们十分开心。但是,仔细看看代码,是不是还有优化的空间呢?我们能不能做的更好,让我们更加开心呢?
那么,我们关注一下下面的代码,思考一下这个问题:
lock(_lock)
{
if (demo == null)
{
demo = new DemoSingleton();
}
}
我们为了让同时进入if条件的只有一个,所以添加了lock锁。那么,我们分析一下代码:i=1时,直接进入if,生成新的实例,同时i=2,i=3...被lock锁限制在外面进行等待,此刻,i=1执行结束。后面的依次进入if条件判断,发现已经存在实例了,直接返回。这样看来,是不是有些耗时呢,明明可以直接返回的,结果还在进行等待,所以我进行了以下的优化:
public static DemoSingleton CreateInstance()
{
if(demo==null)
{
lock (_lock)
{
if (demo == null)
{
demo = new DemoSingleton();
}
}
}
return demo;
}
如上代码,我们就可以在demo已经实例化的时候,无需等待直接返回。
具体什么时候使用?
单例模式的主要作用,是保证整个进程中对象只被实例化一次。
正常情况下,如果一个类型,实例化的时候,非常耗时,或者计算非常复杂。就可以考虑使用单例模式,但是要注意以下的缺点
缺点:一直占有内存(普通实例化,使用的时候创建,用完之后被CLR自动回收)
所以,我们要根据优缺点视情况而定。而不是你一味的使用单例模式,如果电脑的内存不够大,很可能导致系统更加缓慢,或者崩溃。
详细代码示例
///
/// 学习单例模式案例
///
public class DemoSingleton
{
private static DemoSingleton demo;
private static readonly object _lock=new object();
private DemoSingleton()
{
//模拟耗时较长的情况
Thread.Sleep(1000);
Console.WriteLine($"我诞生啦!在{Thread.CurrentThread.ManagedThreadId}中创建");
}
public static DemoSingleton CreateInstance()
{
if(demo==null)
{
lock (_lock)
{
if (demo == null)
{
demo = new DemoSingleton();
}
}
}
return demo;
}
public void Test()
{
Console.WriteLine("DemoSingleton Test 输出");
}
}
其它几种单例模式的示例
理解了上面的案例,下面的应该就更好理解了
public class DemoSingletonStatic
{
private static DemoSingletonStatic demo=new DemoSingletonStatic();
private DemoSingletonStatic()
{
//模拟耗时较长的情况
Thread.Sleep(1000);
Console.WriteLine($"我诞生啦!在{Thread.CurrentThread.ManagedThreadId}中创建");
}
public static DemoSingletonStatic CreateInstance()
{
return demo;
}
}
public class DemoSingletonStaticTWO
{
private static DemoSingletonStaticTWO demo;
private DemoSingletonStaticTWO()
{
//模拟耗时较长的情况
Thread.Sleep(1000);
Console.WriteLine($"我诞生啦!在{Thread.CurrentThread.ManagedThreadId}中创建");
}
static DemoSingletonStaticTWO()
{
demo = new DemoSingletonStaticTWO();
}
public static DemoSingletonStaticTWO CreateInstance()
{
return demo;
}
}
遗留问题:为什么静态类不作为单例模式的经典案例呢?
这个问题我思考了挺久,也在网上找了很多的解释,但是没有一种可以说服我。 目前有一个说法,我觉得还是相比较不错的:
1 .我们第一种经典例子,是在使用的时候创建,而静态类是在项目启动的时候就已经创建了(但是,后面的几个静态的例子,打破了这个。这个观点只适用于第一个)
2.静态类再使用的时候,方法不可以进行重写之类的(这个也有个疑问,我看网上有的人把单例模式中的类型上加了sealed修饰符,完全把这个有点给扼杀掉了。所以目前我也不清楚这个问题)
这个问题,如果有看了这篇文章了解的,还希望分享一下,感谢指导!