小看--单例设计模式

Posted 山的那边是什么

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了小看--单例设计模式相关的知识,希望对你有一定的参考价值。

    (一)单例设计描述

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

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

     --实例成员:只有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

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

     

 

   

 

 

 

     

以上是关于小看--单例设计模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式第五篇-单例模式

单例模式DCL式以及Java指令无序性解析

为什么用枚举类来实现单例模式越来越流行?

为什么用枚举类来实现单例模式越来越流行?

设计模式之单例模式

常用代码片段