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修饰符,完全把这个有点给扼杀掉了。所以目前我也不清楚这个问题)
这个问题,如果有看了这篇文章了解的,还希望分享一下,感谢指导!