.NET 中的双重检查锁定
-
23-08-2019 - |
题
我遇到了这个 文章 讨论为什么 Java 中的双重检查锁定范例被破坏。如果声明了变量,该范式对于 .NET(特别是 C#)是否有效 volatile
?
解决方案
在 C# 中实现单例模式 在第三版中谈到了这个问题。
它说:
使实例变量易失性可以使其工作,就像显式内存屏障调用一样,尽管在后一种情况下,即使专家也无法确切地同意需要哪些屏障。我倾向于尽量避免出现专家无法就正确与错误达成一致的情况!
作者似乎暗示双重锁定比其他策略不太可能起作用,因此不应使用。
其他提示
双重检查锁定现在可以在 Java 和 C# 中使用(Java 内存模型发生了变化,这是影响之一)。然而,你必须得到它 确切地 正确的。如果你把事情搞砸了,哪怕只是一点点,你很可能最终会失去线程安全性。
正如其他答案所述,如果您正在实施 单例模式 有很多更好的方法可以做到这一点。就我个人而言,如果我处于必须在双重检查锁定和“每次锁定”代码之间进行选择的情况,我会每次都选择锁定,直到我得到真正的证据表明它导致了瓶颈。当谈到线程时,一个简单且明显正确的模式非常有价值。
.NET 4.0 有一个新类型: Lazy<T>
这样就消除了对模式错误的担忧。它是新任务并行库的一部分。
请参阅 MSDN 并行计算开发中心: http://msdn.microsoft.com/en-us/concurrency/default.aspx
顺便说一句,.NET 3.5 SP1 有一个向后移植(我相信它不受支持) 这里.
请注意,在 Java 中(也很可能在 .Net 中),单例初始化的双重检查锁定是完全没有必要的,而且也被破坏了。由于类在第一次使用之前不会被初始化,因此已经实现了所需的延迟初始化;
private static Singleton instance = new Singleton();
除非您的 Singleton 类包含可在首次使用 Singleton 实例之前访问的常量之类的内容,否则这就是您需要做的。
我不明白为什么所有人都说双重检查锁定是不好的模式,但不调整代码以使其正常工作。在我看来,下面的代码应该可以正常工作。
如果有人可以告诉我这段代码是否遇到 Cameron 文章中提到的问题,请告诉我。
public sealed class Singleton {
static Singleton instance = null;
static readonly object padlock = new object();
Singleton() {
}
public static Singleton Instance {
get {
if (instance != null) {
return instance;
}
lock (padlock) {
if (instance != null) {
return instance;
}
tempInstance = new Singleton();
// initialize the object with data
instance = tempInstance;
}
return instance;
}
}
}
我已经通过使用布尔值(即使用原语来避免延迟初始化):
使用布尔值的单例不起作用。除非经过内存屏障,否则无法保证不同线程之间的操作顺序。换句话说,从第二个线程可以看出,created = true
可能会在之前执行 instance= new Singleton();
我不太明白为什么双重检查锁定有一堆实现模式(显然是为了解决各种语言中的编译器特性)。关于此主题的 Wikipedia 文章展示了简单的方法和解决该问题的可能方法,但没有一个像这样简单(在 C# 中):
public class Foo
{
static Foo _singleton = null;
static object _singletonLock = new object();
public static Foo Singleton
{
get
{
if ( _singleton == null )
lock ( _singletonLock )
if ( _singleton == null )
{
Foo foo = new Foo();
// Do possibly lengthy initialization,
// but make sure the initialization
// chain doesn't invoke Foo.Singleton.
foo.Initialize();
// _singleton remains null until
// object construction is done.
_singleton = foo;
}
return _singleton;
}
}
在Java中,您可以使用synchronized()而不是lock(),但它基本上是相同的想法。如果单例字段的分配时间可能存在不一致,那么为什么不首先使用本地范围的变量,然后在退出临界区之前的最后可能时刻分配单例字段呢?我错过了什么吗?
@michael-borgwardt 认为,在 C# 和 Java 中,静态字段仅在第一次使用时初始化一次,但是 那 行为是特定于语言的。我经常使用这种模式来延迟初始化集合属性(例如用户.会话)。
我已经通过使用布尔值(即使用原语来避免延迟初始化):
private static Singleton instance;
private static boolean created;
public static Singleton getInstance() {
if (!created) {
synchronized (Singleton.class) {
if (!created) {
instance = new Singleton();
created = true;
}
}
}
return instance;
}