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等方式。