线程安全工厂

Thread safe factory

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

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 不能保证 return 是一个唯一的数字,它不能,因为引用可能是 64 位的,而 GetHashCode return 是一个 32 位的数字。

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