我看到这篇文章讨论了为什么 Java 中的双重检查锁定范式被破坏了。如果声明了变量,范式是否适用于 .NET(特别是 C#)volatile
?
8 回答
双重检查锁定现在可以在 Java 和 C# 中使用(Java 内存模型发生了变化,这是效果之一)。但是,您必须完全正确。如果你把事情弄得一团糟,你很可能最终会失去线程安全性。
正如其他答案所述,如果您正在实现单例模式,那么有更好的方法可以做到这一点。就个人而言,如果我必须在自己实施双重检查锁定和“每次都锁定”代码之间进行选择,我会每次都进行锁定,直到我得到真正的证据表明它会导致瓶颈。在线程方面,一个简单且明显正确的模式很有价值。
.NET 4.0 有一种新类型:Lazy<T>
消除了对模式错误的任何担忧。它是新任务并行库的一部分。
请参阅 MSDN 并行计算开发中心:http: //msdn.microsoft.com/en-us/concurrency/default.aspx
顺便说一句,这里有一个适用于 .NET 3.5 SP1 的后向端口(我相信它不受支持)。
在 C# 中实现单例模式在第三个版本中谈到了这个问题。
它说:
使实例变量 volatile 可以使其工作,就像显式内存屏障调用一样,尽管在后一种情况下,即使专家也无法确切地同意需要哪些屏障。我倾向于避免专家不同意什么是对什么是错的情况!
作者似乎暗示双重锁定比其他策略不太可能起作用,因此不应使用。
请注意,与在 Java 中(最有可能在 .Net 中也是如此)相比,单例初始化的双重检查锁定是完全没有必要的,而且会被破坏。由于类在第一次使用之前不会被初始化,因此所需的延迟初始化已经通过此实现;
private static Singleton instance = new Singleton();
除非您的 Singleton 类包含可以在首次使用 Singleton 实例之前访问的常量之类的东西,否则您需要做的就是这些。
我已经通过使用布尔值(即使用原语来避免延迟初始化)进行了双重检查锁定:
使用布尔值的单例不起作用。除非您通过内存屏障,否则无法保证在不同线程之间看到的操作顺序。换句话说,从第二个线程看,
created = true
可能在执行之前instance= new Singleton();
我不明白为什么所有人都说双重检查锁定是不好的模式,但不要调整代码以使其正常工作。在我看来,下面的代码应该可以正常工作。
如果有人能告诉我这段代码是否存在卡梅伦文章中提到的问题,请告诉我。
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;
}
}
}
我不太明白为什么有一堆关于双重检查锁定的实现模式(显然是为了解决各种语言的编译器特性)。关于这个主题的 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 中,静态字段仅在首次使用时初始化一次,但这种行为是特定于语言的。我经常使用这种模式来延迟初始化集合属性(例如 user.Sessions)。
我已经通过使用布尔值(即使用原语来避免延迟初始化)进行了双重检查锁定:
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;
}