单例模式中的双重检查锁定

21

这可能是一个基础问题。

在多线程环境中,我们可以使用锁来实现单例模式。请参考以下代码片段。但是为什么需要在单例模式中使用双重检查锁定?更重要的是,双重检查锁定是什么意思?

class singleton
{
    private static singleton instance = null;
    private static singleton() { }

    private static object objectlock = new object();

    public static singleton Instance
    {
        get
        {

            lock (objectlock) //single - check lock
            {
                if (instance == null)
                {
                    instance = new singleton();
                }

                return instance;
            }
        }

    }
}

3
实际上,http://csharpindepth.com/Articles/General/Singleton.aspx 就是你需要知道的全部内容。当我准备发表同样的内容时,其他两个人已经赶在我前面了。这充分说明了它的价值。 - Yuck
6个回答

30

Jon Skeet 详细解释了这个问题。

锁定是昂贵的。
如果对象已经存在,那么取出锁就没有意义。
因此,在锁之外首先进行第一次检查。

然而,即使在您获取锁之前对象不存在,另一个线程也可能在if语句和lock语句之间创建它。
因此,您需要在锁内再次进行检查。

但是,编写单例的最佳方法是使用static构造函数:

public sealed class Singleton
{
    private Singleton()
    {
    }

    public static Singleton Instance { get { return Nested.instance; } }

    private class Nested
    {
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static Nested()
        {
        }

        internal static readonly Singleton instance = new Singleton();
    }
} 

1
// 糟糕的代码!不要使用! 第三个版本 - 尝试使用双重检查锁定实现线程安全 - Raghav55
1
抱歉我没有提供链接:http://csharpindepth.com/Articles/General/Singleton.aspx - Raghav55
有人可以向我解释一下如何使用静态构造函数而不使用锁使单例模式线程安全的机制吗?谢谢。 - sc_ray
@sc_ray:在运行初始化程序之前,CLR会为您锁定。 - SLaks
2
我找到了不使用这个解决方案的原因;如果发生异常,您的客户端将看到嵌套在TypeInitializationException中的异常,这将使问题不太明显。因此,我回归到双重检查锁定解决方案(Jon Skeet solution 3)。 - Patrick from NDepend team
显示剩余4条评论

5

使用 .Net 4.x 及更高版本时,尽可能使用 Lazy 类,因为该模式与初始化和发布选项一起使用。(注意:反向选项也可用,其中创建不是线程安全的,但实例的发布是通过发布选项进行的)


5

多线程单例:使用双重检查锁定的最佳方法

public sealed class Singleton
{
   private static volatile Singleton _instance;
   private static readonly object InstanceLoker= new Object();

   private Singleton() {}

   public static Singleton Instance
   {
      get 
      {
         if (_instance == null) 
         {
            lock (InstanceLoker) 
            {
               if (_instance == null) 
                  _instance = new Singleton();
            }
         }

         return _instance;
      }
   }
}

4
我知道的“最佳”方法是这样的:
public class MySingleton {
    // object for synchronization
    private static readonly object syncRoot = new object();
    // the singleton instance
    private static MySingleton @default;

    public static MySingleton Default {
        get {
            // geting singleton instance without locking
            var result = @default;
            // if result is NOT null, no additional action is required
            if ( object.ReferenceEquals(result, null) ){
                // lock the synchronization object
                lock(syncRoot) {
                    // geting singleton instanc in lock - because
                    // the value of @default field could be changed
                    result = @default;

                    // checking for NULL
                    if ( object.ReferenceEquals(result, null) ) {
                        // if result is NULL, create new singleton instance
                        result = new MySingleton();
                        // set the default instance
                        @default = result;
                    }
                }
            }

            // return singleton instance
            return result;
        }
    }
}

1
如果这是您所知道的最好的方法,我建议您阅读提供的链接。 - Daniel Hilgarth
1
这篇文章讲述的单例模式过于复杂。 - SLaks
@Daniel Hilgarth:这是与“第三版-尝试使用双重检查锁定实现线程安全”相同的实现。 - TcKs
@SLack:有两个检查 - 就像链接中的一样。因为在“var result = @default;”和“lock(syncRoot)”之间,另一个线程可以初始化单例值。 - TcKs
@TcKS:正如@SLaks所说:它与第三个版本不同。虽然它非常接近,但您仍应该仔细阅读链接,因为有更好的实现单例模式的方法。 - Daniel Hilgarth
显示剩余3条评论

2
当我们尝试使用并行库执行单例类的方法时,它无法识别单例行为,因为TPL中正在执行多线程,这导致了单例模式的失败。为了解决这个问题,有一个锁定对象的概念,以便一次只有一个线程可以访问它。 但这不是有效的方法,因为涉及锁定检查会对对象创建不必要的监视。为了避免这种情况,我们使用“双重锁定检查”。

enter image description here


2
如果您在字段初始化器中创建对象,则不需要锁定:
class singleton
{
    private static singleton instance = new singleton();
    private static singleton() { }

    public static singleton Instance
    {
        get { return instance; }
    }
}

还要记住,锁只控制对象的创建,如果在多个线程中使用该对象,则仍需要使对象具有线程安全性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接