最近我发现this article解释了如何在C#中实现thread safe singleton pattern
。
但是,我想知道让单例线程安全有多么重要,使单例类非线程安全有什么危险,又有什么危险呢?考虑一个非常简单,非线程安全的单例模式实现:
public class ThreadNotSafeSingleton
{
public static ThreadNotSafeSingleton Instance
{
get
{
if (_instance == null)
{
_instance = new ThreadNotSafeSingleton();
}
return _instance;
}
}
private static ThreadNotSafeSingleton _instance;
private ThreadNotSafeSingleton()
{
}
}
现在让我们说,由于该代码是非线程安全的,因此两个线程将输入此行代码_instance = new ThreadNotSafeSingleton();
。在这种情况下,第一个线程将初始化_instance
字段,然后第二个线程将再次初始化_instance
字段,结果您的应用程序中将只有一个_instance
存在。
那么,这有什么大不了的呢?我知道,如果您有更复杂的代码,并且构造函数运行的其他代码可能不是线程安全的,则可能很危险。但是,如果您的单例就这么简单,那么非线程安全是否会有任何问题?
答案 0 :(得分:3)
由于单例模式将类限制为仅一个实例,因此您的方法违反了该模式。
如果出于某种原因这对于您的实现来说是可以的,并且我们遵循您陈述的示例,则两个并发访问中的每一个都将获得另一个ArrayList <Person> list = new ArrayList<Person>();
实例的可能性很高。如果编译器认为不需要在返回之前回读刚写入的ThreadNotSafeSingleton
变量,则可能由于优化而发生这种情况。这种优化行为由C#实现的内存模型定义。
经常引用_instance
关键字作为一种可能的解决方案,但是当线程1通过volatile
行时,它并不能解决同步问题(如BionicCode所指出的)。进入睡眠状态,然后线程2也会评估相同,并且实例化单例。当线程1稍后唤醒时,它将实例化另一个单例。
答案 1 :(得分:3)
Singleton模式的目的是根据Type创建仅一个对象。
想象一下thread1
和thread2
首次进入get
属性的Instance
访问器 ({{1}仍_instance
)同时 :
null
检查thread1
表达式,发现if (_instance == null)
是_instance
,并且同时,null
检查thread2
表达式,发现if
也是_instance
。 null
来到这个thread1
表达式,并从_instance = new ThreadNotSafeSingleton();
类型创建一个新对象。因为ThreadNotSafeSingleton
检查thread2
表达式并发现if
也是_instance
,所以来到null
代码块并创建一个新对象,该对象将覆盖引用if
变量的值。最后,_instance
使用的对象与thread1
的对象不同。
当thread2
为_instance
且两个或多个线程尝试同时获取您的Type实例时,会发生此问题。您必须限制同时访问线程的null
代码主体(线程同步),尤其是当if
为_instace
时。