在.NET中进行双重检查锁定

时间:2008-12-27 11:05:42

标签: .net multithreading paradigms double-checked-locking

我遇到了这个article讨论为什么在Java中打破双重检查锁定范例的原因。如果变量被声明为volatile,那么范例是否对.NET(特别是C#)有效?

8 个答案:

答案 0 :(得分:72)

双重检查锁定现在可以在Java和C#中使用(Java内存模型已更改,这是其中一种效果)。但是,你必须正确 。如果你稍微搞砸了,你最终可能会失去线程的安全性。

正如其他答案所述,如果您正在实施singleton pattern,那么有更好的方法可以做到这一点。就个人而言,如果我处于需要在双重检查锁定和“每次锁定”代码之间做出选择的情况,我每次都要锁定,直到我得到真正的证据表明它导致了瓶颈。当涉及到线程时,一个简单而明显正确的模式是值得的。

答案 1 :(得分:25)

Implementing the Singleton Pattern in C#在第三版中讨论了这个问题。

它说:

  

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

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

答案 2 :(得分:24)

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

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

顺便说一句,对于.NET 3.5 SP1,有一个backport(我相信它不受支持)here

答案 3 :(得分:7)

注意比在Java中(并且很可能在.Net中),单一初始化的双重检查锁定完全没有必要以及破坏。由于类在第一次使用之前未初始化,因此已经实现了所需的延迟初始化;

private static Singleton instance = new Singleton();

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

答案 4 :(得分:3)

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

如果有人能告诉我此代码是否存在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;
        }
    }
}

答案 5 :(得分:2)

  

我通过使用布尔值(即使用原语避免延迟初始化)得到了双重检查锁定工作:

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

之前执行

答案 6 :(得分:1)

我不太明白为什么在双重检查锁定上有很多实现模式(显然是为了解决各种语言中的编译器特性)。关于这个主题的维基百科文章显示了天真的方法和解决问题的可能方法,但没有一个像这样简单(在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)。

答案 7 :(得分:-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;
}