繁体   English   中英

线程安全 C# 单例模式

[英]Thread Safe C# Singleton Pattern

我有一些关于这里记录的单例模式的问题: 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;
      }
   }

执行锁很贵吗?

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

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

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

上面的实现对于大多数情况来说已经足够好了,但此时最好去阅读Jon Skeet 关于 C#中的单例的文章,该文章还评估了其他替代方案。

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) 或更新版本。

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

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

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

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

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

(顺便说一句,如果您可以只使用private static volatile Singleton instance = new Singleton()或者如果您可以不使用单例而是使用静态类,则两者都更好地解决这些问题)。

原因是性能。 如果instance != null (除了第一次,情况总是如此),就不需要做一个代价高昂的lock :同时访问初始化单例的两个线程将不必要地同步。

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

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

杰弗里·里希特 (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;
            }
        }
    }

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

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

您可以急切地创建一个线程安全的 Singleton 实例,具体取决于您的应用程序需要,这是简洁的代码,但我更喜欢 @andasa 的惰性版本。

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

    private Singleton() { }

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

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

private static readonly Singleton singleInstance = new Singleton();

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

“单例设计模式中的 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);
        }
    }

从主要:

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

抗反射单例模式:

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