我有一个非常基本的问题: 假设我有以下注册为单例的类和一个方法。当
Create()
被多个线程命中时会发生什么?所有线程是否同时访问此方法?如果 10 个线程调用此方法,一旦方法范围结束,是否会创建 10 个 numbers
变量并释放掉?
由于此类没有任何类级属性,我认为它是线程安全的?
// registration
builder.Services.AddSingleton<ISingleton, SingletonTest>();
public interface ISingleton
{
int Create(int number);
}
public class SingletonTest : ISingleton
{
public int Create(int number)
{
List<int> numbers = new List<int>();
numbers.Add(number);
return numbers.Count;
}
}
所有线程是否同时访问此方法?
是的,没有额外的同步技术。 Singleton 就像创建静态成员并从多个线程访问它。
public static readonly ISingleton Instance = new SingletonTest();
如果 10 个线程调用此方法,一旦方法范围结束,是否会创建 10 个数字变量并释放掉?
是的,将创建 10 个
numbers
,但离开此方法后不会释放。释放是由运行时线程完成的,该线程执行垃圾收集器,释放未引用的已分配内存。
由于此类没有任何类级属性,我认为它是线程安全的?
是的,它是线程安全的。如果你有属性,但不可变,初始化一次,例如在构造函数中,它也将是线程安全的。
注册为 Singleton 的类与直接控制
Create
方法没有任何关系。通过将类注册为单例,您得到的唯一承诺是,无论有多少线程需要该依赖项,也无论是否同时需要它,都只会获得 SingletonTest
的单个(吨)实例已创建。
一旦创建了该实例,它就会传递给任何需要它的人,然后他们就可以用它做任何他们想做的事情。如果它被编写为线程安全的,那么多个线程同时使用它就没有问题 - 如果不是,那么你就会遇到一些麻烦,并且 Singleton 状态不会有任何帮助。
在您的示例中,具体而言,
Create
方法非常不是线程安全。为什么?好吧, List
并不意味着是线程安全的,并且您允许多个线程同时访问它,这意味着事情可能会中断。您可以将列表切换为 ConcurrentBag
,这将解决您的问题,因为它公开了与 List
几乎相同的功能,只是它意味着同时使用。
如果您想思考 为什么 列表可能不是线程安全的,请记住
List<T>
实例由数组 (T[]
) 支持 - 我们称之为 arr
。第一个 Add
调用有点像 arr[0] = value
,第二个有点像 arr[1] = value
,等等。但是列表有一个内部状态,即它知道其中有多少项,所以下一个索引是什么应该是下次有人打电话的时候Add
,所以在我们的例子中,操作实际上更像是
arr[this.currentIndex] = value;
this.currentIndex += 1;
现在,如果 2 个线程尝试同时执行此操作会发生什么?经典的竞争条件 - 两者都会看到相同的
this.currentIndex
值,因此两者都会设置相同的数组元素,然后它们都会将该值增加 1。这并不是列表工作方式的非常准确的表示,但这只是一个简单的例子,说明未编写并发使用的代码如何在并发线程下崩溃。