懶加載Lazy 之LazyThreadSafetyMode


1、創建懶加載的實例類

internal class MyClass
{
    //用於測試構造函數被調用了多少次,以及各對象的HashCode
    //使用線程安全隊列准確獲取數據
    public static ConcurrentQueue<int> List = new ConcurrentQueue<int>();
    public MyClass()
    {
        List.Enqueue(this.GetHashCode());
    }
}

2、測試各模式下的數據

2.1、LazyThreadSafetyMode.None
[Fact]
public void LazyThreadSafetyMode_None_ShouldThrowException()
{
    Lazy<MyClass> lazyObj = new Lazy<MyClass>(() => { return new MyClass(); }, LazyThreadSafetyMode.None);
    //用於記錄實際創建的對象的HashCode
    ConcurrentQueue<int> result = new ConcurrentQueue<int>();
    for (int i = 0; i < 10; i++)
    {
        Thread th = new Thread(() =>
        {
            try
            {
                result.Enqueue(lazyObj.Value.GetHashCode());
            }
            catch (InvalidOperationException ex)
            {
                ex.Message.ShouldBe("ValueFactory attempted to access the Value property of this instance.");
            }
        });
        th.Start();
    }
}

當Mode參數為LazyThreadSafetyMode.None時,得到的結果如下:

這錯誤原因是沒有創建MyClass實例前,就有線程去訪問對象的GetHashCode()方法了。這也說明了如果選用LazyThreadSafetyMode.None,那就不保證線程在訪問Lazy對象的lazyObj.Value前先創建對象。這種情況下,當有些線程在啟動前,很幸運的,該對象已經創建了,有些線程啟動前則很不幸,對象並沒有創建,因此會出現上面的那種異常。

2.2、LazyThreadSafetyMode.PublicationOnly
[Fact]
public void LazyThreadSafetyMode_PublicationOnly_ShouldUseOne()
{
    Lazy<MyClass> lazyObj = new Lazy<MyClass>(() => { return new MyClass(); }, LazyThreadSafetyMode.PublicationOnly);
    //用於記錄實際創建的對象的HashCode
    ConcurrentQueue<int> result = new ConcurrentQueue<int>();
    for (int i = 0; i < 10; i++)
    {
        Thread th = new Thread(() =>
        {
            result.Enqueue(lazyObj.Value.GetHashCode());
        });
        th.Start();
    }
    Thread.Sleep(100);
    result.Distinct().Count().ShouldBe(1);
    //構造函數被調用了多次
    //但是最終使用的僅有一個,多線程情況不保證哪個最先被使用
    MyClass.List.ToArray().Distinct().Count().ShouldBeGreaterThan(1);
}

當Mode參數為LazyThreadSafetyMode.PublicationOnly時,得到的結果如下:

可能每次運行的結果不同,但這次運行中可以看到,MyClass的構造函數被運行了3次,但是即使有3個對象創建了,在每次調用該對象時,只使用其中的一個對象。(不確定使用的是最先生成的那個對象,因為測試的時候,發現有時候使用的對象是后生成的。不過根據本人理解應該是使用最先生成的對象,由於在並發過程中,最先生成的對象不一定最先打印出來)

2.3、LazyThreadSafetyMode.ExecutionAndPublication
[Fact]
public void LazyThreadSafetyMode_ExecutionAndPublication_ShouldOnlyOne()
{
    Lazy<MyClass> lazyObj = new Lazy<MyClass>(() => { return new MyClass(); }, LazyThreadSafetyMode.ExecutionAndPublication);
    //用於記錄實際創建的對象的HashCode
    ConcurrentQueue<int> result = new ConcurrentQueue<int>();
    for (int i = 0; i < 10; i++)
    {
        Thread th = new Thread(() =>
        {
            result.Enqueue(lazyObj.Value.GetHashCode());
        });
        th.Start();
    }
    Thread.Sleep(100);
    result.Distinct().Count().ShouldBe(1);
    //僅僅調用了構造函數一次,保證了單例
    MyClass.List.ToArray().Count().ShouldBe(1);
}

當Mode參數為LazyThreadSafetyMode.ExecutionAndPublication時,得到的結果如下:

使用LazyThreadSafetyMode.ExecutionAndPublication,保證了對象只被創建一次。基於這個特性,可以使用LazyThreadSafetyMode.ExecutionAndPublication方式,實現在多線程下的單例模式。但是要注意:此時Lazy對象僅保證多個線程訪問的是同一個對象,但不保證多線程訪問對象時候是否同步,因此,如果要確保多線程訪問同步訪問同一個對象,最好還是采取lock等方式。


免責聲明!

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



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