饿汉模式
单例模式又被称为单件模式,这个模式作用是保持程序中只有`唯一`对象,一听到唯一,那肯定就明白了,无非就是不让别人创建新对象呗,只需要两点就可以
1.私有化构造函数,
2.创建一个静态对象属性以便外部使用
这么简单还算一种模式?别急,我们慢慢看,首先,我们创建一个单例
class Singleton
{
//静态对象属性
public static Singleton SingletonInstance { get; } = new Singleton();
//私有化构造函数
private Singleton()
{
Console.WriteLine("Singleton构造");
}
}
挺简单的吗这不是,的确,挺简单的,这就是单例模式其中之一的饿汉模式
,什么意思呢,
饿汉模式:在
程序启动
或单例类被加载
时,就实例化单例模式
但是这么做不感觉有问题吗?假如这个类我们并不使用或在程序启动很久以后我们才使用,那么这个对象的预创建不就很浪费吗?并且如果这个对象的创建需要很大的资源,那....,所以我们需要延迟单例对象的创建.
懒汉模式
将对象延迟到第一次访问时才创建,这种被称为`懒汉模式`
懒汉模式:当第一次访问单例对象时才去实例化对象
看起来也挺简单的样子,无非是将对象实例化放在属性的get
中
class Singleton
{
private static Singleton _singleton=null;
//静态对象属性
public static Singleton SingletonInstance
{
get
{
if (_singleton == null)
{//如果对象不存在则创建
_singleton = new Singleton();
}
return _singleton;
}
}
//私有化构造函数
private Singleton()
{
Console.WriteLine("Singleton构造");
}
}
看起来感觉挺不错的样子,但是真是这样吗?来做一个多线程的例子看看.
static void Main(string[] args)
{
Task.Run(() => { Singleton singleton1 = Singleton.SingletonInstance;});
Task.Run(() => { Singleton singleton1 = Singleton.SingletonInstance;});
Task.Run(() => { Singleton singleton1 = Singleton.SingletonInstance;});
Task.Run(() => { Singleton singleton1 = Singleton.SingletonInstance;});
Console.ReadKey();
}
开4个线程进行测试
Task.Run() 是从线程池中获取线程进行使用
可以看到对于多线程来说这个单例完全无用,解决多线程的办法就是加锁,所以需要在实例化对象进行加锁
private static object objLock = new object();
//静态对象属性
public static Singleton SingletonInstance
{
get
{
lock (objLock)
{
if (_singleton == null)
{//如果对象不存在则创建
_singleton = new Singleton();
}
}
return _singleton;
}
}
但是都知道,加锁会极大的影响性能,每一次都判断锁感觉上是一种极大的浪费.,然后再次优化就出现了经典的单例实例实现双重检查锁
public static Singleton SingletonInstance
{
get
{
if (_singleton == null)
{//解决锁的效率问题.
lock (objLock)
{
if (_singleton == null)
{//如果对象不存在则创建
_singleton = new Singleton();
}
}
}
return _singleton;
}
}
在外面加一个if判断,这层if看起来是多余的,但是它极大的节省了性能,杜绝了大多数无多并发时锁的验证,从而提高了性能.
C#单例另一种实现---延迟加载
在C#中有一个Lazy类,这个类是一个延迟加载类,也就是自动为我们实现延迟加载功能,并且还是线程安全的,也就是说完全可以利用这个类实现单例
class SingletonLazy
{
//Lazy需要一个无参有返的委托,
public static Lazy<SingletonLazy> SingletonInstance = new Lazy<SingletonLazy>(() => { return new SingletonLazy(); });
//私有化构造函数
private SingletonLazy()
{
Console.WriteLine("SingletonLazy构造");
}
}
Lazy构造器有一个参数是传入一个无参有返的委托,整好可以实例化对象,
static void Main(string[] args)
{
SingletonLazy singletonLazy = null;
Console.WriteLine("延迟");
Task.Run(() => { SingletonLazy singleton1 = SingletonLazy.SingletonInstance.Value; });
Task.Run(() => { SingletonLazy singleton1 = SingletonLazy.SingletonInstance.Value; });
Task.Run(() => { SingletonLazy singleton1 = SingletonLazy.SingletonInstance.Value; });
Task.Run(() => { SingletonLazy singleton1 = SingletonLazy.SingletonInstance.Value; });
Console.ReadKey();
}
对这个单例进行测试,测试结果与刚才无异,在工作中很多都是使用这种方式来实现单例模式
Lazy
下面来看看Lazy的实现机制,其实我们也大致能想到内部到底是如何处理的
public class Lazy<T>
{
//内部类,存储数据的类
private class Boxed
{
internal T m_value;
internal Boxed(T value)
{
m_value = value;
}
}
private object m_boxed;
//委托
private Func<T> m_valueFactory;
//线程安全对象
private object m_threadSafeObj = new object();
//委托默认值
private static readonly Func<T> ALREADY_INVOKED_SENTINEL = () => default(T);
public Lazy(Func<T> valueFactory)
{
m_valueFactory = valueFactory ?? throw new ArgumentNullException("valueFactory");
}
//Value
public T Value
{
get
{
Boxed boxed = null;
if (m_boxed != null)
{
boxed = (m_boxed as Boxed);
if (boxed != null)
{
return boxed.m_value;
}
throw new Exception();
}
return LazyInitValue();
}
}
//初始化Value值
private T LazyInitValue()
{
Boxed boxed = null;
//读取安全对象
object obj = Volatile.Read<object>(ref m_threadSafeObj);
bool flag = false;
try
{
//如果是第一次,则开启锁并创建对象
if (ALREADY_INVOKED_SENTINEL !=obj)
{
Monitor.Enter(obj, ref flag);
}
if (m_boxed == null)
{
//创建Boxed对象并将m_threadSafeObj设置为ALREADY_INVOKED_SENTINEL
boxed = (Boxed)(m_boxed = CreateValue());
Volatile.Write<object>(ref m_threadSafeObj, (object)ALREADY_INVOKED_SENTINEL);
}
else
{//已存在对象
boxed = (m_boxed as Boxed);
}
}
finally
{
if (flag)
{
Monitor.Exit(obj);
}
}
return boxed.m_value;
}
//创建Boxed对象
private Boxed CreateValue()
{
if (m_valueFactory != null)
{
try
{
Func<T> valueFactory = m_valueFactory;
if (valueFactory == ALREADY_INVOKED_SENTINEL)
{
return null;
}
return new Boxed(valueFactory());
}
catch (Exception)
{
throw;
}
}
try
{
//创建默认对象
return new Boxed((T)Activator.CreateInstance(typeof(T)));
}
catch (MissingMethodException)
{
throw;
}
}
}
上面是简化版的Lazy源码,可以看到Lazy也只是利用了一个内部类Boxed对象缓存了数据,代码中有一点有意思的,在LazyInitValue()方法中使用了Volatile类读取数据进行加锁,volatile是保持多线程下数据同步的问题,一种简单方式可以在变量中加上volatile关键字
多个线程同时访问一个变量时,CLR(Common Language Runtime)为了效率会进行相应优化,比如“允许线程进行本地缓存”,这样就可能导致变量访问的不一致性。volatile就是为了解决这个问题;volatile修饰的变量,不允许线程进行本地缓存,每个线程的读写都直接操作在共享内存上,这就保证了变量始终具有一致性
单例模式定义
单例模式保证在系统中一个类仅有一个实例对象