前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C#设计模式——(创建型-单例设计模式)

C#设计模式——(创建型-单例设计模式)

原创
作者头像
用户10053120
发布2022-09-27 09:10:30
4790
发布2022-09-27 09:10:30
举报
文章被收录于专栏:C# 设计原则

一、单例设计模式应用场景

打开手机应用、电脑应用、windows任务管理器时,永远只有一个对象实例,这是为了节省资源,提高效率。

二、饿汉式单例设计模式

不推荐使用,程序一加载,还没调用就准备好了对象,会造成内存资源的浪费。

代码语言:javascript
复制
public class SingleHUngry
{
    //1、构造函数私有化
   private SingleHUngry()
   {}
   //2、创建唯一对象,private:迪米特原则,没有必要暴露给外面的成员,都写成private
   //static 静态成员,保证在内存的唯一性
   //readonly 不允许修改
   private static readonly SingleHUngry _singleHUngry=new SingleHUngry();
   //3、为外界提供获取唯一对象的能力
   public static SingleHUngry()
   {
       return _singleHungry;  
   }  
 }
代码语言:javascript
复制
//创建实例时做三件事:1、在内存中开辟空间;2、执行对象的构造函数、创建对象;3、把我的内存空间指向我创建的对象;
SingleHUngry s1=new SingleHungry();
SingleHUngry s2=new SingleHungry();
Console.WriteLine("s1.GetHashCode");
Console.WriteLine("s2.GetHashCode");

三、懒汉式单例设计模式

当你需要创建对象时,才创建对象,不会造成内存资源的浪费。

多线程使用时会出现线程安全的问题。

代码语言:javascript
复制
public class LazyManSingle
{
     //1、构造函数私有化
     private LazyManSingle()
     {}
     //2、创建唯一对象,
     public static LazyManSingle lazy;
     //3、为外界提供获取唯一对象的能力
     public static LazyManSingle GetLazyManSingle()
     {
          if(lazy==null)
          {
          lazy=new LazyManSingle();
          Console.WriteLine("我只执行一次");
          }
          return lazy;
     }
}
代码语言:javascript
复制
//懒汉式会有线程安全问题
public static Main(string[]args)
{
    for(int i=0;i<10;i+=)
    {
        new Thread(()=>LazyManSingle.GetLazyManSingle()).Start();
    }
}

通过加锁来解决多线程下单例不安全的问题。

代码语言:javascript
复制
public class LazyManSingle
{
     //1、构造函数私有化
     private LazyManSingle()
     {}
     //2、创建唯一对象,
     //volatile 容易改变的,不稳定的,不确定的。用volatitle标记的成员可以避免指令重排
     public volatile static LazyManSingle lazy;
     //创建锁
      private static object =new object();
     //3、为外界提供获取唯一对象的能力
     public static LazyManSingle GetLazyManSingle()
     {
          //lock是C#的语法糖
          //Monitor.Enter(),Monitor.Exit();互斥锁,用来解决多线程的安全问题
          if(lazy==null)//不加双重锁会消耗系统资源
          {
          lock(0)
          {
               if(lazy==null)
               {
               //new 1,在内存中开辟空间;2,执行构造函数创建对象;3,把空间指向创建的对象。
               lazy=new LazyManSingle();
               Console.WriteLine("我只执行一次");
               }
          }
          }
          return lazy;
     }
}

四、反射和单例

代码语言:javascript
复制
public static Main(string[]args)
{
    //1。通过单例来创建对象
    LazyManSingle l1=LazyManSingle.GetLazyManSingle();
    //LazyManSingle l2=LazyManSingle.GetLazyManSingle();
    //2.通过反射来破坏单例————》通过反射来调用私有的构造函数
    Type t=Type.GetType("命名空间的名字.LazyManSingle");
    //获取私有构造函数
    ConstructorInfo[] conts=t.GetConstructors(BindingFlags.NonPublic|BindingFlags.Instance);
    //通过反射来创建对象
    LazyManSingle l2=(LazyManSingle)conts[0].Invoke(null);
    //反射破坏的单例,二者的哈希值不一致
    Console.WriteLine(l1.GetHashCode());
    Console.WriteLine(l2.GetHashCode());
    Console.ReadKey();    
}
代码语言:javascript
复制
public class LazyManSingle
{
     //1、构造函数私有化
     private LazyManSingle()
     {
          lock(0)
          {
          //if被执行,说明有反射来搞破坏
          if(lazy!=null)
               {
               throw  new Exception("不要试图用反射来破坏我的单例");
               }
          }

     }
     //2、创建唯一对象,
     //volatile 容易改变的,不稳定的,不确定的。用volatitle标记的成员可以避免指令重排
     public volatile static LazyManSingle lazy;
     //创建锁
      private static object o =new object();
     //3、为外界提供获取唯一对象的能力
     public static LazyManSingle GetLazyManSingle()
     {
          //lock是C#的语法糖
          //Monitor.Enter(),Monitor.Exit();互斥锁,用来解决多线程的安全问题
          if(lazy==null)//不加双重锁会消耗系统资源
          {
          lock(o)
          {
               if(lazy==null)
               {
               //new 1,在内存中开辟空间;2,执行构造函数创建对象;3,把空间指向创建的对象。
               lazy=new LazyManSingle();
               Console.WriteLine("我只执行一次");
               }
          }
          }
          return lazy;
     }
}

若两个对象都是通过反射来创建的,也会破坏单例

代码语言:javascript
复制
public static Main(string[]args)
{
    //1。通过单例来创建对象
    //LazyManSingle l1=LazyManSingle.GetLazyManSingle();
    //LazyManSingle l2=LazyManSingle.GetLazyManSingle();
    //2.通过反射来破坏单例————》通过反射来调用私有的构造函数
    Type t=Type.GetType("命名空间的名字.LazyManSingle");
    //获取私有构造函数
    ConstructorInfo[] conts=t.GetConstructors(BindingFlags.NonPublic|BindingFlags.Instance);
    //通过反射来创建两个对象,会破坏单例
    //原因是:new没有被执行,反射跨过了new,直接去执行构造函数
    LazyManSingle l1=(LazyManSingle)conts[0].Invoke(null);
    //创建完成后,在私有的构造函数中,私有的标记位已经变为true
    //如通过反射来进行破坏,通过反射来拿到私有字段,把值在创建第二个对象之前改成false
    FieldInfo fieldInfo=t.GetField("isOK",BindingFlags.NonPublic|BindingFlags.Static);
    fieldINfo.SetValue("isOK",false);
    LazyManSingle l2=(LazyManSingle)conts[0].Invoke(null);
    //反射破坏的单例,二者的哈希值不一致
    Console.WriteLine(l1.GetHashCode());
    Console.WriteLine(l2.GetHashCode());
    Console.ReadKey();  
}
代码语言:javascript
复制
public class LazyManSingle
{
     //搞一个标记位,来解决反射破坏单例的问题
     private static bool isOK-false;
     //1、构造函数私有化
     private LazyManSingle()
     {
          lock(0)
          {
               if(isOK==false)
               {
                    isOK=true;
               }
               else
               {
                    throw new Exception("我把反射给堵截了~~~~~~");
               }
          }

     }
     //2、创建唯一对象,
     //volatile 容易改变的,不稳定的,不确定的。用volatitle标记的成员可以避免指令重排
     public volatile static LazyManSingle lazy;
     //创建锁
      private static object o =new object();
     //3、为外界提供获取唯一对象的能力
     public static LazyManSingle GetLazyManSingle()
     {
          //lock是C#的语法糖
          //Monitor.Enter(),Monitor.Exit();互斥锁,用来解决多线程的安全问题
          if(lazy==null)//不加双重锁会消耗系统资源
          {
          lock(o)
          {
               if(lazy==null)
               {
               //new 1,在内存中开辟空间;2,执行构造函数创建对象;3,把空间指向创建的对象。
               lazy=new LazyManSingle();
               Console.WriteLine("我只执行一次");
               }
          }
          }
          return lazy;
     }
}

可以通过反射拿到私有字段isOK

代码语言:javascript
复制
    //如通过反射来进行破坏,通过反射来拿到私有字段,把值在创建第二个对象之前改成false
    FieldInfo fieldInfo=t.GetField("isOK",BindingFlags.NonPublic|BindingFlags.Static);
    fieldINfo.SetValue("isOK",false);

五、内部静态类实现单例

代码语言:javascript
复制
public class HungryClass
{
    public static HUngryClass GetSingleHUngry()
    {
        return InnerClass.hc;
    }
    //内部静态类,不会跟着外部的HungryClass一起加载到内存中,
    //只有在外部调用GetSIngleHUngry方法时,才回加载内部成员
    public static class InnerClass
    {
        public static readonly HungryClass hc=new HungryClass();
    }
}

六、总结

  • 懒汉式的缺点:只适合单线程应用,多线程会出现线程安全问题;

加双重锁定、Volatile保证不会出现指令重排;

反射会破坏单例,有办法去堵。

  • 饿汉式的缺点

类加载的时候,直接创建对象,没有实现“懒加载”,可能会造成内存浪费。

内部静态类


注:本文为自老赵Net的视频课学习笔记

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档