zoukankan      html  css  js  c++  java
  • 线程安全单例最佳实践,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); }
  • 相关阅读:
    《那些年啊,那些事——一个程序员的奋斗史》——26
    《那些年啊,那些事——一个程序员的奋斗史》——26
    《那些年啊,那些事——一个程序员的奋斗史》——26
    Linux系统网络性能实例分析
    数据库服务器的性能调优-续
    数据库服务器的性能调优
    Linux 虚存 linux2.6内核特性
    Linux 虚存的性能问题
    linux系统性能监控--网络利用率
    IIS Express服务器遇到400/503/IIS Express Error的解决办法
  • 原文地址:https://www.cnblogs.com/Gool/p/9420870.html
Copyright © 2011-2022 走看看