一、单例设计模式应用场景
打开手机应用、电脑应用、windows任务管理器时,永远只有一个对象实例,这是为了节省资源,提高效率。
二、饿汉式单例设计模式
不推荐使用,程序一加载,还没调用就准备好了对象,会造成内存资源的浪费。
public class SingleHUngry
{
//1、构造函数私有化
private SingleHUngry()
{}
//2、创建唯一对象,private:迪米特原则,没有必要暴露给外面的成员,都写成private
//static 静态成员,保证在内存的唯一性
//readonly 不允许修改
private static readonly SingleHUngry _singleHUngry=new SingleHUngry();
//3、为外界提供获取唯一对象的能力
public static SingleHUngry()
{
return _singleHungry;
}
}
//创建实例时做三件事:1、在内存中开辟空间;2、执行对象的构造函数、创建对象;3、把我的内存空间指向我创建的对象;
SingleHUngry s1=new SingleHungry();
SingleHUngry s2=new SingleHungry();
Console.WriteLine("s1.GetHashCode");
Console.WriteLine("s2.GetHashCode");
三、懒汉式单例设计模式
当你需要创建对象时,才创建对象,不会造成内存资源的浪费。
多线程使用时会出现线程安全的问题。
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;
}
}
//懒汉式会有线程安全问题
public static Main(string[]args)
{
for(int i=0;i<10;i+=)
{
new Thread(()=>LazyManSingle.GetLazyManSingle()).Start();
}
}
通过加锁来解决多线程下单例不安全的问题。
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;
}
}
四、反射和单例
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();
}
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;
}
}
若两个对象都是通过反射来创建的,也会破坏单例
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();
}
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
//如通过反射来进行破坏,通过反射来拿到私有字段,把值在创建第二个对象之前改成false
FieldInfo fieldInfo=t.GetField("isOK",BindingFlags.NonPublic|BindingFlags.Static);
fieldINfo.SetValue("isOK",false);
五、内部静态类实现单例
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 删除。