非线程安全单例的危险是什么?
What are the dangers of non thread safe singleton?
最近我发现 this article 解释了如何在 C# 中实现 thread safe singleton pattern
。
但是,我想知道使您的单例线程安全有多重要以及让您的单例 class 非线程安全的危险是什么。考虑一个非常简单的非线程安全的单例模式实现:
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
。
那么这里有什么大不了的呢?我知道,如果您有更复杂的代码,并且您的构造函数运行一些其他可能不是线程安全的代码,那可能很危险。但是如果你的单例那么简单,非线程安全有什么问题吗?
由于单例模式将 class 限制为只有一个实例,您的方法违反了该模式。
如果出于任何原因这对您的实施来说是可行的,并且我们遵循您陈述的示例,那么两个并发访问中的每一个都很有可能获得 ThreadNotSafeSingleton
的另一个实例。如果编译器决定不需要回读它在返回之前写入的 _instance
变量,则可能由于优化而发生这种情况。此优化行为由 C# 实现的内存模型定义。
volatile
关键字经常被引用为可能的解决方案,但它不会解决同步问题(正如 BionicCode 所指出的),当线程 1 通过 if (_instance == null)
行并进入睡眠状态,然后线程 2 也评估相同的 if 并开始实例化单例。当线程 1 稍后醒来时,它将实例化另一个单例。
Singleton 模式的目的是从一种类型中仅创建一个对象。
假设 thread1
和 thread2
第一次访问 Instance
属性 的 get
访问器 (_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
为 null
并且两个或多个线程同时尝试获取您的类型的实例时,会出现此问题。您必须限制线程同时访问 if
代码体(线程同步),尤其是当您的 _instace
还 null
时。
最近我发现 this article 解释了如何在 C# 中实现 thread safe singleton pattern
。
但是,我想知道使您的单例线程安全有多重要以及让您的单例 class 非线程安全的危险是什么。考虑一个非常简单的非线程安全的单例模式实现:
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
。
那么这里有什么大不了的呢?我知道,如果您有更复杂的代码,并且您的构造函数运行一些其他可能不是线程安全的代码,那可能很危险。但是如果你的单例那么简单,非线程安全有什么问题吗?
由于单例模式将 class 限制为只有一个实例,您的方法违反了该模式。
如果出于任何原因这对您的实施来说是可行的,并且我们遵循您陈述的示例,那么两个并发访问中的每一个都很有可能获得 ThreadNotSafeSingleton
的另一个实例。如果编译器决定不需要回读它在返回之前写入的 _instance
变量,则可能由于优化而发生这种情况。此优化行为由 C# 实现的内存模型定义。
volatile
关键字经常被引用为可能的解决方案,但它不会解决同步问题(正如 BionicCode 所指出的),当线程 1 通过 if (_instance == null)
行并进入睡眠状态,然后线程 2 也评估相同的 if 并开始实例化单例。当线程 1 稍后醒来时,它将实例化另一个单例。
Singleton 模式的目的是从一种类型中仅创建一个对象。
假设 thread1
和 thread2
第一次访问 Instance
属性 的 get
访问器 (_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
为 null
并且两个或多个线程同时尝试获取您的类型的实例时,会出现此问题。您必须限制线程同时访问 if
代码体(线程同步),尤其是当您的 _instace
还 null
时。