2011-11-09 24 views
4

當設計引用另一個對象的類時,僅在第一次使用引用對象時創建引用對象可能會有所幫助。使用延遲加載。延遲加載和Thread.MemoryBarrier的使用

我經常用這個模式來創建一個延遲加載屬性:據我可以

Encoding Utf8NoBomEncoding { 
    get { 
    if (this.utf8NoBomEncoding == null) { 
     var encoding = new UTF8Encoding(false); 
     Thread.MemoryBarrier(); 
     this.utf8NoBomEncoding = encoding; 
    } 
    return this.utf8NoBomEncoding; 
    } 
} 

Encoding utf8NoBomEncoding; 

Encoding Utf8NoBomEncoding { 
    get { 
    return this.utf8NoBomEncoding ?? 
     (this.utf8NoBomEncoding = new UTF8Encoding(false)); 
    } 
} 

那時,我發現這個代碼瀏覽了BCL的源代碼時就告訴這些都不是線程安全的。例如。可能會創建多個Encoding對象。我完全明白,並且知道如果創建額外的Encoding對象,這不是問題。它是不可變的,很快就會被垃圾收集。

但是,我很想知道爲什麼Thread.MemoryBarrier是必要的,以及第二種實現與多線程場景中的第一種實現不同。

顯然,如果線程安全性是一個問題最好的實現可能使用Lazy<T>

Lazy<Encoding> lazyUtf8NoBomEncoding = 
    new Lazy<Encoding>(() => new UTF8Encoding(false)); 

Encoding Utf8NoBomEncoding { 
    get { 
    return this.lazyUtf8NoBomEncoding.Value; 
    } 
} 
+1

創建多個'Encoding'對象不成問題,因爲所有副本的行爲都是相同的。 (還請注意,在編寫代碼時,'懶惰'不可用。) –

回答

6

該代碼將不會使內存屏障一場災難。仔細看這些代碼行。

var encoding = new UTF8Encoding(false); 
    Thread.MemoryBarrier(); 
    this.utf8NoBomEncoding = encoding; 

現在,設想一些其他線程看到最後一行的效果,但沒有看到第一行的效果。那將是一場完全的災難。

內存屏障確保任何看到encoding的線程都可以看到其構造函數的所有效果。

例如,沒有存儲器的屏障,所述第一行和最後一行可在內部優化(大致)如下:
1)分配一些存儲器,存儲指向它在this.utf8NoBomEncoding
2)調用UTF8Encoding構造函數使用有效值填充該內存。

想象一下,如果在步驟1和2之間另一個線程運行並通過此代碼。它將使用尚未構造的對象。

+0

感謝您的回答。這是否意味着我的問題中的第一個代碼示例可以被優化成可能會像您描述的那樣失敗的東西? –

+0

是的。沒有任何東西可以確保對象在其他線程可用之前是有效的。 –

+0

正確,JIT可以自由地對指令進行重新排序,只要從單個線程的角度來看,它們看起來是相同的順序。這就是爲什麼在沒有使用內存屏障(或易失性)的情況下,雙重檢查鎖定習慣被打破的原因,出於同樣的原因。 –

2

這種模式在.NET中相當普遍。這是可能的,因爲UTF8Encoding是一個不可變的類。是的,可以創建該類的多個實例,但這並不重要,因爲所有實例都是相同的。用Equals()覆蓋強制執行。額外的副本將很快被垃圾收集。內存屏障只是確保對象狀態完全可見。

+0

當您查看來自信譽良好的源代碼並執行某些操作的代碼時,可能會產生誤導,您認爲自己做的是同樣的事情,但其他代碼實際上僅適用於某些在註釋或任何內容中未提及的荒謬細微的細節。這只是意味着。 –

+0

Feh,反射器不是一個有信譽的來源:) –