線程安全單例最佳實踐,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); }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM