1.0 c#设计模式 --单例模式

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修饰符,完全把这个有点给扼杀掉了。所以目前我也不清楚这个问题)

这个问题,如果有看了这篇文章了解的,还希望分享一下,感谢指导!

 

posted @ 2020-03-01 14:06  keke..lele  阅读(162)  评论(0编辑  收藏  举报