zoukankan      html  css  js  c++  java
  • 小看--单例设计模式

        (一)单例设计描述

          只要了解过设计模式的同学都会知道:单例设计模式,大家都知道单例设计模式是一种创建行的设计模式。既然是创建型,那么先来讲讲,对象的创建的过程吧。

         --静态成员:静态成员在程序加载的时候,就会加载进内存。

         --实例成员:只有new的时候才有实例成员。1、为实例的数据字段分配内存,然后初始化对象的附加字段(类型指针和同步索引块),最后调用类型的实例构造器来设置对象的初始化状态。

          单例模式:一般用在一个类的创建对象很消耗资源,消耗时间,并且系统要保证只有一个对象的时候。一句话,对象的创建并不是很耗时间,不要刻意去套用单例模式,单例模式必须是在单例的时候,才单例。

        (二) 单例模式的演变

            下面我们来模拟实际情况

     public class Singleton {
            /// <summary>
            /// 对象会持有资源
            /// </summary>
            private List<string> _connList=new List<string>()
            {
                "测试数据库连接","测试数据库连接2","测试数据库连接3","测试数据库连接4"
            };
    
            public Singleton()
            {
                long lResult = 0;
                for (int i = 0; i < 100000; i++){
                    lResult += i;
                }
                Thread.Sleep(1000);
                Console.WriteLine("{0}被构造一次",this.GetType().Name);
            }
    
            public void Show()
            {
                Console.WriteLine("调用了Show");
            }
    
    
        }

          这个类的创建需要耗费很多资源,里面有个Show方法。

          那么接下来,实际中,我们可能有十个地方要用到这个类里面的Show方法,我们的做法是这样的

      //那么接下来,我们这里要调用十次Show方法
                for (var i = 0; i < 10; i++) {
                    var singletonObj = new Singleton();
                    singletonObj.Show();
                }

          

        这里每次调用一次,都需要耗费很多资源和时间,这里可能有些同学就会说,那我把这个singletonObj=new Singleton()提取出来,放到最外面来。

        那行,按照我们需要,我们把var singletonObj=new Singletone()放到外面,如下所示

       

      貌似这样就解决了我们的问题,但是各位你们想一想,我们一个系统是有多个人开发的,A这里这样做,B可能不知道这里有这个声明,那他可能就还是一样去New Singleton,还是导致我们系统中,存在大量这个对象。

      我们应该要如何解决这个问题呢? 如何保证这个对象在整个系统只被创建一次呢?

      单例模式--单线程

      从上面的问题,我们也可以看出因为谁都可以在New Singleton,所以导致了这个问题。那按照这个想法,那我们就想啦,那就把构造函数私有化呗,私有化完了之后,我们应该还要提供一个方法或者啥的,给外面调用(也只能是静态的成员),构造函数私有化了,外面是不可以New了的

       那就按照,刚刚的说法,我们来进行一次改进

    public class Singleton {
            /// <summary>
            /// 对象会持有资源
            /// </summary>
            private List<string> _connList=new List<string>()
            {
                "测试数据库连接","测试数据库连接2","测试数据库连接3","测试数据库连接4"
            };
    
    
            private Singleton(){
                long lResult = 0;
                for (int i = 0; i < 100000; i++){
                    lResult += i;
                }
                Thread.Sleep(1000);
                Console.WriteLine("{0}被构造一次",this.GetType().Name);
            }
    
            public static Singleton CreateInstance(){
                return new Singleton();
            }
    
            public void Show()
            {
                Console.WriteLine("调用了Show");
            }
    
        }

        按照我们上面这个写法,把构造函数私有化了,然后在静态方法里面New Singletone();

        调用结果如下:

     for (var i = 0; i < 10; i++) {
                    var singletonObj = Singleton.CreateInstance();
                    singletonObj.Show();
     }  //写进里面去了,是为了模拟有十个不同的开发,再调用

     

      那结果,还是没有达到我们想要的,那现在问题就是,对象没有重用,因为我们每次new,导致了对象没有做到重用,那就让对象进行重用呗。最简单的方法,就是给一个静态的字段(为啥静态呢,因为我们那边方法是静态的),然后做一个判断,如果对象为空,那么我们就创建,如果不为空,就不用创建了,如下所示。    

       

       我们在原来的基础上,做了如上图的改进。结果如下,

       

       我们想要的结果实现了,多次调用的时候,做了重用对象,只构造了一次。本来想着单例模式就这样结束了(单线程是没有问题,并且这种实现是一种懒汉式,懒汉式:当你需要用这个类的时候,才会去实例化)。

       编程世界里面,我们总是要考虑一下,多线程的情况。

       单例模式--多线程

       这个是用Task.Run()开启了也给多线程的异步调用

     
                for (var i = 0; i < 10; i++){
                    Task.Run(()=>{
                        var singletonObj = Singleton.CreateInstance();
                        singletonObj.Show();
                    });      
                }
                Thread.Sleep(5000);

       

       通过上面的结果,我们可以看出,我们的之前的做法,在多线程环境还是会调用多次。

       有些同学就会说用lock锁啦,行,我们就给他加上一把锁。

    public class Singleton {
            /// <summary>
            /// 对象会持有资源
            /// </summary>
            private List<string> _connList=new List<string>()
            {
                "测试数据库连接","测试数据库连接2","测试数据库连接3","测试数据库连接4"
            };
    
            private static Singleton singletonObj = null;
            private static readonly object singleTonObjLock = new object();  //加锁,之后这里为啥要用readonly,大家可以找
    
            private Singleton(){
                long lResult = 0;
                for (int i = 0; i < 100000; i++){
                    lResult += i;
                }
                Thread.Sleep(1000);
                Console.WriteLine("{0}被构造一次",this.GetType().Name);
                Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}调用一次");
            }
    
            public static Singleton CreateInstance()
            {
                lock (singleTonObjLock) //很多同学都说用lock this,this肯定是不行的,因为lock是lock引用的,如果这个this的引用改变了...
                {
                    if (singletonObj == null)
                    {
                        singletonObj = new Singleton();
                    }
                }
                return singletonObj;
            }
    
            public void Show()
            {
                Console.WriteLine("调用了Show");
            }
    
        }

       看我们给他加完锁的时候效果。

       

       嗯,实现了我们想要的效果了,说明我们加锁是有效果的。到了这个时候,大家可能觉得一个单例模式应该就快结束了,那么我们再来看看这种情况。

      单例模式--多线程(双if+lock)

      

     通过上面的介绍,我们理解了单例模式的演变过程,也对单例模式,多线程有了更加深刻的印象。

     (三)单例模式其他实现

        就像我们一开始说的那样,单例模式,其实一个进程内,在多线程环境下,如何保证只有一个对象,这就是单例。也可以从这个定义看出,我们可以通过静态的构造函数来实现一个单例模式。

        静态构造函数,是由CLR保证的,有且只会加载一次。

        其他很多方法实现,都是利用static关键字的背后原因,在第一次使用类型之前被调用,且只会被调用一次。

       (四)懒汉式,饿汉式

          懒汉,就是说这个人很懒,需要用的时候,才构建。双if+lock这种就属于懒汉式。懒汉式,利用了延迟加载加载的思想。

          饿汉:就是调用我这个类型,就会帮你创建好;管你用不用,我都会帮你创建;就是饿了吗,我后面介绍的利用static关键字的就是属于饿汉式;饿汉式:管你之前有没有(内存中),都会帮你new一个新的实例。

       (五)单例模式的使用场景

           单例:必须单例才单例,反正没必要。单例模式实现都有性能,损失,静态方法。

           单例:会把对象常驻内存,静态的。

           单例的使用,多个人操作可能会对你影响,因为都是对同一份引用进行修改。

           一般用在数据库连接,打印机,远程服务调用,等等这些大对象身上。

       //单例模式的应用场景补充:读取配置文件信息,读取配置文件不应该一直new一个类,应该是单例的。

           

          

       public class ConfigV1
        {
            private static object _lockObj = new object();
            private static ConfigV1 _instance;
    
            private static Dictionary<string,string> _configItems =new Dictionary<string, string>();
    
            private ConfigV1()
            {
                Init();
                Console.WriteLine($"{this.GetType().Name}被构造一次");
            }
    
            public static  ConfigV1 GetInstance()
            {
                if (_instance == null)
                {
                    lock (_lockObj)
                    {
                        if (_instance == null)
                        {
                            _instance=new ConfigV1();
                        }
                    }
                }
                return _instance;
            }
    
    
            public string  this[string item]
            {
                get
                {
                    return _configItems[item];
                }
            }
    
            private  void Init()
            {
                 var config=ConfigurationManager.AppSettings.AllKeys;
                 foreach(var key in config)
                {
                    _configItems.Add(key,ConfigurationManager.AppSettings[key]);
                }
            }
    
             
    
    
        }

           github:https://github.com/gdoujkzz/DesignPattern.git

      谢谢你阅读我的博客,如果有收获,请点一个赞(推荐)

         

       

         

  • 相关阅读:
    如何在Windows 7平台搭建Android Cocos2d-x3.2alpha0开发环境
    boost::unordered_map分析和使用(转)
    #、##、__VA_ARGS__和##__VA_ARGS__的作用(转)
    C++ explicit关键字详解(转)
    C++11的auto关键字
    Nagios监控系统
    CentOS6.5安装oracle11
    Centos7系统中Mysql登录忘记root密码
    在Centos7系统上查看Mysql版本的方法
    忘记Redhat7root密码
  • 原文地址:https://www.cnblogs.com/gdouzz/p/8319485.html
Copyright © 2011-2022 走看看