简体   繁体   English

线程安全 C# 单例模式

[英]Thread Safe C# Singleton Pattern

I have some questions regarding the the singleton pattern as documented here: http://msdn.microsoft.com/en-us/library/ff650316.aspx我有一些关于这里记录的单例模式的问题: http : //msdn.microsoft.com/en-us/library/ff650316.aspx

The following code is an extract from the article:以下代码摘自文章:

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;
      }
   }
}

Specifically, in the above example, is there a need to compare instance to null twice, before and after the lock?具体来说,在上面的例子中,是否需要在锁定之前和之后将instance与null进行两次比较? Is this necessary?这是必要的吗? Why not perform the lock first and make the comparison?为什么不先执行锁定并进行比较?

Is there a problem in simplifying to the following?简化为以下有问题吗?

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

         return instance;
      }
   }

Is the performing the lock expensive?执行锁很贵吗?

Performing the lock is terribly expensive when compared to the simple pointer check instance != null .与简单的指针检查instance != null相比,执行锁定是非常昂贵的。

The pattern you see here is called double-checked locking .您在此处看到的模式称为双重检查锁定 Its purpose is to avoid the expensive lock operation which is only going to be needed once (when the singleton is first accessed).它的目的是避免只需要一次的昂贵的锁操作(当第一次访问单例时)。 The implementation is such because it also has to ensure that when the singleton is initialized there will be no bugs resulting from thread race conditions.实现是这样的,因为它还必须确保在初始化单例时不会有线程竞争条件导致的错误。

Think of it this way: a bare null check (without a lock ) is guaranteed to give you a correct usable answer only when that answer is "yes, the object is already constructed".可以这样想:只有当答案是“是的,对象已经构造”时,才能保证裸null检查(没有lock )为您提供正确的可用答案。 But if the answer is "not constructed yet" then you don't have enough information because what you really wanted to know is that it's "not constructed yet and no other thread is intending to construct it shortly ".但是,如果答案是“尚未构建”,那么您没有足够的信息,因为您真正想知道的是“尚未构建,并且没有其他线程打算很快构建它”。 So you use the outer check as a very quick initial test and you initiate the proper, bug-free but "expensive" procedure (lock then check) only if the answer is "no".因此,您将外部检查用作非常快速的初始测试,并且仅当答案为“否”时才启动正确的、无错误但“昂贵”的过程(锁定然后检查)。

The above implementation is good enough for most cases, but at this point it's a good idea to go and read Jon Skeet's article on singletons in C# which also evaluates other alternatives.上面的实现对于大多数情况来说已经足够好了,但此时最好去阅读Jon Skeet 关于 C#中的单例的文章,该文章还评估了其他替代方案。

The Lazy<T> version: 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() { }
}

Requires .NET 4 and C# 6.0 (VS2015) or newer.需要 .NET 4 和 C# 6.0 (VS2015) 或更新版本。

Performing a lock: Quite cheap (still more expensive than a null test).执行锁定:相当便宜(仍然比空测试贵)。

Performing a lock when another thread has it: You get the cost of whatever they've still to do while locking, added to your own time.当另一个线程拥有它时执行锁定:您获得了锁定时他们仍然要做的任何事情的成本,增加了您自己的时间。

Performing a lock when another thread has it, and dozens of other threads are also waiting on it: Crippling.当另一个线程拥有它时执行锁定,并且许多其他线程也在等待它:Crippling。

For performance reasons, you always want to have locks that another thread wants, for the shortest period of time at all possible.出于性能原因,您总是希望在尽可能短的时间内拥有另一个线程想要的锁。

Of course it's easier to reason about "broad" locks than narrow, so it's worth starting with them broad and optimising as needed, but there are some cases that we learn from experience and familiarity where a narrower fits the pattern.当然,“宽”锁比“窄”锁更容易推理,因此值得从“宽”锁开始并根据需要进行优化,但在某些情况下,我们可以从经验和熟悉中学到更适合模式的窄锁。

(Incidentally, if you can possibly just use private static volatile Singleton instance = new Singleton() or if you can possibly just not use singletons but use a static class instead, both are better in regards to these concerns). (顺便说一句,如果您可以只使用private static volatile Singleton instance = new Singleton()或者如果您可以不使用单例而是使用静态类,则两者都更好地解决这些问题)。

The reason is performance.原因是性能。 If instance != null (which will always be the case except the very first time), there is no need to do a costly lock : Two threads accessing the initialized singleton simultaneously would be synchronized unneccessarily.如果instance != null (除了第一次,情况总是如此),就不需要做一个代价高昂的lock :同时访问初始化单例的两个线程将不必要地同步。

In almost every case (that is: all cases except the very first ones), instance won't be null.在几乎所有情况下(即:除第一个之外的所有情况), instance都不会为空。 Acquiring a lock is more costly than a simple check, so checking once the value of instance before locking is a nice and free optimization.获取锁比简单的检查成本更高,因此在锁定之前检查一次instance的值是一种很好且免费的优化。

This pattern is called double-checked locking: http://en.wikipedia.org/wiki/Double-checked_locking这种模式称为双重检查锁定: http : //en.wikipedia.org/wiki/Double-checked_locking

Jeffrey Richter recommends following:杰弗里·里希特 (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;
            }
        }
    }

This is called Double checked locking mechanism, first, we will check whether the instance is created or not.这称为双重检查锁定机制,首先,我们将检查实例是否已创建。 If not then only we will synchronize the method and create the instance.如果不是,那么我们将同步方法并创建实例。 It will drastically improve the performance of the application.它将极大地提高应用程序的性能。 Performing lock is heavy.执行锁定很重。 So to avoid the lock first we need to check the null value.所以为了避免锁定首先我们需要检查空值。 This is also thread safe and it is the best way to achieve the best performance.这也是线程安全的,它是实现最佳性能的最佳方式。 Please have a look at the following code.请看下面的代码。

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;
        }
    }
}

You could eagerly create the a thread-safe Singleton instance, depending on your application needs, this is succinct code, though I would prefer @andasa's lazy version.您可以急切地创建一个线程安全的 Singleton 实例,具体取决于您的应用程序需要,这是简洁的代码,但我更喜欢 @andasa 的惰性版本。

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

    private Singleton() { }

    public static Singleton Instance()
    {
        return instance;
    }
}

Another version of Singleton where the following line of code creates the Singleton instance at the time of application startup. Singleton 的另一个版本,其中以下代码行在应用程序启动时创建 Singleton 实例。

private static readonly Singleton singleInstance = new Singleton();

Here CLR (Common Language Runtime) will take care of object initialization and thread safety.这里 CLR(公共语言运行时)将负责对象初始化和线程安全。 That means we will not require to write any code explicitly for handling the thread safety for a multithreaded environment.这意味着我们不需要显式编写任何代码来处理多线程环境的线程安全。

"The Eager loading in singleton design pattern is nothing a process in which we need to initialize the singleton object at the time of application start-up rather than on demand and keep it ready in memory to be used in future." “单例设计模式中的 Eager 加载不是我们需要在应用程序启动时而不是按需初始化单例对象并使其在内存中准备好以备将来使用的过程。”

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);
        }
    }

from main :从主要:

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");
        }

Reflection resistant Singleton pattern:抗反射单例模式:

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");
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM