微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

线程安全工厂

如何解决线程安全工厂

我有一个工厂,它的工作是在需要时在每次调用时创建新实例。我的代码看起来像这样:

public class Factory
{
    public object Get()
        => new object();
}

我尝试用这样的 nunit 测试来测试多线程部分:

public async Task Get_ThreadSaftyTests()
{
    const int limit = 10_000
    var concurrentCollection = new ConcurrentBag<object>();
    var instance = new Factory();
    var tasks = new List<Task>(limit);

    for (int i = 0; i < limit; i++)
    {
        tasks.Add(Task.Factory.StartNew(() =>
        {
            concurrentCollection.Add(instance.Get());
        }));
    }

    await Task.WhenAll(tasks);

    var hashSet = new HashSet<long>();
    foreach (var item in concurrentCollection)
    {
        hashSet.Add(item.GetHashCode());
    }

    Assert.AreEqual(limit,hashSet.Count);
}

该测试大部分时间通过。准确地说是 4/5。这有点奇怪。

我尝试在返回实例之前实现锁定,如下所示:

public class Factory
{
    private static readonly Lazy<object> lockObject = new Lazy<object>(true);
    
    public object Get()
    {
        lock (lockObject.Value)
        {
            return new object();
        }
    }
}

但是有了这个实现,测试几乎永远不会通过。 1/5 运行测试通过。这里的问题是我做错了什么?

------------------------------更新---------------- ---------------

如果循环有 100 000 次迭代,则在两种情况下每次测试都会失败。

如果循环有 6500 次迭代,它在两种情况下每次都会通过。

解决方法

    lock (lockObject.Value)
    {
        return new object();
    }

在这种情况下,锁什么都不做。调用构造函数可能是也可能不是线程安全的,但是框架中没有任何东西使创建对象成为非线程安全的。 IE。您需要自己编写一个非线程安全的构造函数,然后由您来正确使用它。

然而:

var hashSet = new HashSet<long>();
foreach (var item in concurrentCollection)
{
    hashSet.Add(item.GetHashCode());
}

GetHashCode 不能保证返回唯一的数字,它不能保证,因为引用可能是 64 位的,而 GetHashCode 返回一个 32 位的数字。

修复应该很简单。更改为 HashSet<object> 并删除 .GetHashCode()

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。