yan7

饿汉模式

单例模式又被称为单件模式,这个模式作用是保持程序中只有`唯一`对象,一听到唯一,那肯定就明白了,无非就是不让别人创建新对象呗,只需要两点就可以

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() 是从线程池中获取线程进行使用

1536140732087

可以看到对于多线程来说这个单例完全无用,解决多线程的办法就是加锁,所以需要在实例化对象进行加锁

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();
}

1536236190027

对这个单例进行测试,测试结果与刚才无异,在工作中很多都是使用这种方式来实现单例模式

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修饰的变量,不允许线程进行本地缓存,每个线程的读写都直接操作在共享内存上,这就保证了变量始终具有一致性

单例模式定义

单例模式保证在系统中一个类仅有一个实例对象

相关文章: