我遇到了这个 文章 讨论为什么 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;
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top