58

我看到这篇文章讨论了为什么 Java 中的双重检查锁定范式被破坏了。如果声明了变量,范式是否适用于 .NET(特别是 C#)volatile

4

8 回答 8

79

双重检查锁定现在可以在 Java 和 C# 中使用(Java 内存模型发生了变化,这是效果之一)。但是,您必须完全正确。如果你把事情弄得一团糟,你很可能最终会失去线程安全性。

正如其他答案所述,如果您正在实现单例模式,那么有更好的方法可以做到这一点。就个人而言,如果我必须在自己实施双重检查锁定和“每次都锁定”代码之间进行选择,我会每次都进行锁定,直到我得到真正的证据表明它会导致瓶颈。在线程方面,一个简单且明显正确的模式很有价值。

于 2008-12-27T11:47:35.520 回答
30

.NET 4.0 有一种新类型:Lazy<T>消除了对模式错误的任何担忧。它是新任务并行库的一部分。

请参阅 MSDN 并行计算开发中心:http: //msdn.microsoft.com/en-us/concurrency/default.aspx

顺便说一句,这里有一个适用于 .NET 3.5 SP1 的后向端口(我相信它不受支持)。

于 2010-05-25T23:46:37.560 回答
27

在 C# 中实现单例模式在第三个版本中谈到了这个问题。

它说:

使实例变量 volatile 可以使其工作,就像显式内存屏障调用一样,尽管在后一种情况下,即使专家也无法确切地同意需要哪些屏障。我倾向于避免专家不同意什么是对什么是错的情况!

作者似乎暗示双重锁定比其他策略不太可能起作用,因此不应使用。

于 2008-12-27T11:21:12.670 回答
10

请注意,与在 Java 中(最有可能在 .Net 中也是如此)相比,单例初始化的双重检查锁定是完全没有必要的,而且会被破坏。由于类在第一次使用之前不会被初始化,因此所需的延迟初始化已经通过此实现;

private static Singleton instance = new Singleton();

除非您的 Singleton 类包含可以在首次使用 Singleton 实例之前访问的常量之类的东西,否则您需要做的就是这些。

于 2008-12-27T11:32:27.093 回答
3

我已经通过使用布尔值(即使用原语来避免延迟初始化)进行了双重检查锁定:

使用布尔值的单例不起作用。除非您通过内存屏障,否则无法保证在不同线程之间看到的操作顺序。换句话说,从第二个线程看, created = true可能在执行之前instance= new Singleton();

于 2011-05-27T15:38:29.313 回答
3

我不明白为什么所有人都说双重检查锁定是不好的模式,但不要调整代码以使其正常工作。在我看来,下面的代码应该可以正常工作。

如果有人能告诉我这段代码是否存在卡梅伦文章中提到的问题,请告诉我。

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;
        }
    }
}
于 2015-04-13T20:05:58.860 回答
-1

我不太明白为什么有一堆关于双重检查锁定的实现模式(显然是为了解决各种语言的编译器特性)。关于这个主题的 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)。

于 2012-06-13T01:36:59.540 回答
-5

我已经通过使用布尔值(即使用原语来避免延迟初始化)进行了双重检查锁定:

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;
}
于 2011-02-28T17:23:02.060 回答