线程安全单例最佳实践,C#中的Lazy是如何保证线程安全的

在.NET 4.0之后,.NET Framework中提供了一种安全的延迟加载类型Lazy
Lazy能够在多线程环境下,保证GetValue函数只执行一次,从而实现单例模式

在过去,实现单例模式我们通常使用二次判断锁,或者利用类的静态初始化函数
利用Lazy类型,能够简化这一过程,并且性能上更好

Lazy创建的时候可以指定线程安装模式,目前有两种模式,PublicationOnly,ExcutionAndPublication

 

PublicationOnly模式

                boxed = CreateValue(); //1
                if (boxed == null ||
                    Interlocked.CompareExchange(ref m_boxed, boxed, null) != null) //2
                {
                    boxed = (Boxed)m_boxed; //3
                }
                else
                {
                    m_valueFactory = ALREADY_INVOKED_SENTINEL; //4
                }

 

 

1.运行初始化函数,装箱到一个内部Box类型中,解决null值判断的问题,如果已经创建的情况,会返回null,该过程是线程不安全的

2.判断m_boxed是否为空,m_boxed是value保存的字段,如果等于空则设置为boxed,该方法能保证原子性,该过程是线程安全的

3.如果CreateValue返回空,表示其他线程已经创建有实例,则设置为已经创建好的实例

4.将初始化方法标记为已经初始化,一般发生在并发运行情况下,多次运行CreateValue

 

 

PublicationOnly模式下使用基于Interlocked.CompareExchange实现的乐观锁,该类包含了原子性方法 CAS(Compare and swap)

CAS是利用CPU提供的原子性指令来实现,不同运行时版本可能有不一样实现
Interlocked具体的实现在Native方法中,有兴趣的朋友可以通过coreclr/jvm代码查看具体实现

这种模式下,单例函数可能多次运行,但是最终能保证获取到的实例只有一个

 

ExcutionAndPublication模式下使用的是Volatile+MonitorMonitor就是lock语句的实现,Monitor实现在Native代码中,是重量级的锁

Monitor支持队列和线程睡眠,能够保证一整个方法块处于单线程执行状态

                object threadSafeObj = Volatile.Read(ref m_threadSafeObj); //强制从主内存空间同步变量到线程内存空间副本
                bool lockTaken = false;
                try
                {
                    if (threadSafeObj != (object)ALREADY_INVOKED_SENTINEL) //此时会有多个线程获取到正确值,抢夺开始
                        Monitor.Enter(threadSafeObj, ref lockTaken); //尝试等待锁,进入成功设置lockTaken为true
                    else
                        Contract.Assert(m_boxed != null);
             //单线程代码块 Start
                    if (m_boxed == null) //没有设置值的情况
                    {
                        boxed = CreateValue(); //获取值
                        m_boxed = boxed; //设置到字段中
                        Volatile.Write(ref m_threadSafeObj, ALREADY_INVOKED_SENTINEL); //强制将线程内存空间副本写入到主内存空间
                    }
                    else // got the lock but the value is not null anymore, check if it is created by another thread or faulted and throw if so
                    {
                        boxed = m_boxed as Boxed;
                        if (boxed == null) // it is not Boxed, so it is a LazyInternalExceptionHolder
                        {
                            LazyInternalExceptionHolder exHolder = m_boxed as LazyInternalExceptionHolder;
                            Contract.Assert(exHolder != null);
                            exHolder.m_edi.Throw();
                        }
                    }
            //单线程代码块End }
finally { if (lockTaken) //进入成功需要释放,避免死锁 Monitor.Exit(threadSafeObj); }

 

posted @ 2018-08-05 05:50  幻影gool  阅读(1996)  评论(0编辑  收藏  举报