懒加载Lazy<T>之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等方式。

posted @ 2021-04-15 23:46  boydenyol  阅读(1281)  评论(0编辑  收藏  举报