93

我对这里记录的单例模式有一些疑问:http: //msdn.microsoft.com/en-us/library/ff650316.aspx

以下代码摘自文章:

using System;

public sealed class Singleton
{
   private static volatile Singleton instance;
   private static object syncRoot = new object();

   private Singleton() {}

   public static Singleton Instance
   {
      get 
      {
         if (instance == null) 
         {
            lock (syncRoot) 
            {
               if (instance == null) 
                  instance = new Singleton();
            }
         }

         return instance;
      }
   }
}

具体来说,在上面的例子中,是否需要在锁之前和之后将 instance 与 null 进行两次比较?这是必要的吗?为什么不先执行锁定并进行比较?

简化为以下是否有问题?

   public static Singleton Instance
   {
      get 
      {
        lock (syncRoot) 
        {
           if (instance == null) 
              instance = new Singleton();
        }

         return instance;
      }
   }

执行锁是否昂贵?

4

10 回答 10

148

与简单的指针检查相比,执行锁定非常instance != null昂贵。

您在此处看到的模式称为双重检查锁定。它的目的是避免昂贵的锁操作,这种操作只需要一次(当第一次访问单例时)。实现是这样的,因为它还必须确保在初始化单例时不会出现线程竞争条件导致的错误。

可以这样想:只有当答案nulllock“是的,对象已经被构造”时,才能保证给你一个正确的可用答案。但是,如果答案是“尚未构建”,那么您没有足够的信息,因为您真正想知道的是它“尚未构建,并且没有其他线程打算在短期内构建它”。因此,您将外部检查用作非常快速的初始测试,并且仅当答案为“否”时才启动正确的、无错误但“昂贵”的过程(锁定然后检查)。

上面的实现对于大多数情况来说已经足够好了,但是在这一点上,阅读Jon Skeet 的关于 C#中的单例的文章是个好主意,该文章还评估了其他替代方案。

于 2012-09-07T10:38:19.327 回答
42

Lazy<T>版本:

public sealed class Singleton
{
    private static readonly Lazy<Singleton> lazy
        = new Lazy<Singleton>(() => new Singleton());

    public static Singleton Instance
        => lazy.Value;

    private Singleton() { }
}

需要 .NET 4 和 C# 6.0 (VS2015) 或更高版本。

于 2017-05-11T05:57:09.593 回答
16

执行锁定:相当便宜(仍然比空测试更昂贵)。

当另一个线程拥有它时执行锁定:您将获得他们在锁定时仍然要做的任何事情的成本,添加到您自己的时间。

当另一个线程拥有它时执行一个锁,并且几十个其他线程也在等待它:Crippling。

出于性能原因,您总是希望在尽可能短的时间内拥有另一个线程想要的锁。

当然,“宽”锁比窄锁更容易推理,因此值得从宽锁开始并根据需要进行优化,但在某些情况下,我们从经验和熟悉中学习到窄锁适合模式。

(顺便说一句,如果你可以只使用private static volatile Singleton instance = new Singleton()或者你可以不使用单例而是使用静态类,那么在这些问题方面两者都更好)。

于 2012-09-07T10:47:02.703 回答
7

原因是性能。如果instance != null(除了第一次之外,情况总是如此),则无需执行昂贵的操作lock:同时访问已初始化的单例的两个线程将不必要地同步。

于 2012-09-07T10:37:07.343 回答
4

在几乎所有情况下(即:除了第一个情况之外的所有情况),instance都不会为空。获取锁比简单的检查成本更高,因此检查一次instance锁定前的值是一种不错且免费的优化。

这种模式称为双重检查锁定:http ://en.wikipedia.org/wiki/Double-checked_locking

于 2012-09-07T10:37:35.890 回答
3

Jeffrey Richter 建议如下:



    public sealed class Singleton
    {
        private static readonly Object s_lock = new Object();
        private static Singleton instance = null;
    
        private Singleton()
        {
        }
    
        public static Singleton Instance
        {
            get
            {
                if(instance != null) return instance;
                Monitor.Enter(s_lock);
                Singleton temp = new Singleton();
                Interlocked.Exchange(ref instance, temp);
                Monitor.Exit(s_lock);
                return instance;
            }
        }
    }

于 2016-03-23T08:58:59.520 回答
2

这称为双重检查锁定机制,首先,我们将检查实例是否创建。如果不是,那么只有我们将同步方法并创建实例。它将大大提高应用程序的性能。执行锁定很重。所以为了避免锁首先我们需要检查空值。这也是线程安全的,是实现最佳性能的最佳方式。请看下面的代码。

public sealed class Singleton
{
    private static readonly object Instancelock = new object();
    private Singleton()
    {
    }
    private static Singleton instance = null;

    public static Singleton GetInstance
    {
        get
        {
            if (instance == null)
            {
                lock (Instancelock)
                {
                    if (instance == null)
                    {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
}
于 2019-07-09T05:18:36.583 回答
1

您可以根据您的应用程序需求急切地创建一个线程安全的 Singleton 实例,这是简洁的代码,尽管我更喜欢@andasa 的惰性版本。

public sealed class Singleton
{
    private static readonly Singleton instance = new Singleton();

    private Singleton() { }

    public static Singleton Instance()
    {
        return instance;
    }
}
于 2018-07-01T02:14:06.357 回答
0

Singleton 的另一个版本,其中以下代码行在应用程序启动时创建 Singleton 实例。

private static readonly Singleton singleInstance = new Singleton();

这里 CLR(公共语言运行时)将负责对象初始化和线程安全。这意味着我们不需要显式编写任何代码来处理多线程环境的线程安全。

“单例设计模式中的急切加载不是我们需要在应用程序启动时而不是按需初始化单例对象并将其保存在内存中以备将来使用的过程。”

public sealed class Singleton
    {
        private static int counter = 0;
        private Singleton()
        {
            counter++;
            Console.WriteLine("Counter Value " + counter.ToString());
        }
        private static readonly Singleton singleInstance = new Singleton(); 

        public static Singleton GetInstance
        {
            get
            {
                return singleInstance;
            }
        }
        public void PrintDetails(string message)
        {
            Console.WriteLine(message);
        }
    }

从主要:

static void Main(string[] args)
        {
            Parallel.Invoke(
                () => PrintTeacherDetails(),
                () => PrintStudentdetails()
                );
            Console.ReadLine();
        }
        private static void PrintTeacherDetails()
        {
            Singleton fromTeacher = Singleton.GetInstance;
            fromTeacher.PrintDetails("From Teacher");
        }
        private static void PrintStudentdetails()
        {
            Singleton fromStudent = Singleton.GetInstance;
            fromStudent.PrintDetails("From Student");
        }
于 2019-07-11T03:48:05.490 回答
0

抗反射单例模式:

public sealed class Singleton
{
    public static Singleton Instance => _lazy.Value;
    private static Lazy<Singleton, Func<int>> _lazy { get; }

    static Singleton()
    {
        var i = 0;
        _lazy = new Lazy<Singleton, Func<int>>(() =>
        {
            i++;
            return new Singleton();
        }, () => i);
    }

    private Singleton()
    {
        if (_lazy.Metadata() == 0 || _lazy.IsValueCreated)
            throw new Exception("Singleton creation exception");
    }

    public void Run()
    {
        Console.WriteLine("Singleton called");
    }
}
于 2021-01-02T20:00:13.720 回答