线程安全C#单例模式

时间:2012-09-07 10:35:25

标签: c# design-patterns singleton

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

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

简化以下是否存在问题?

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

         return instance;
      }
   }

执行锁定是否昂贵?

10 个答案:

答案 0 :(得分:112)

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

您在此处看到的模式称为double-checked locking。其目的是避免昂贵的锁定操作,这只需要一次(当首次访问单例时)。实现是这样的,因为它还必须确保在初始化单例时不会因为线程竞争条件而产生错误。

以这种方式思考:只有当答案为“是,对象已经构建”时,才能保证只有null检查(没有lock)才能给出正确的可用答案。但如果答案是“尚未构建”,那么你没有足够的信息,因为你真正想知道的是“它还没有构建而且没有其他线程打算很快构建它” 。因此,您使用外部检查作为一个非常快速的初始测试,并且只有在答案为“否”时才启动正确的,无错误但“昂贵”的程序(锁定然后检查)。

对于大多数情况,上述实现已经足够好了,但是在这一点上,最好先阅读Jon Skeet's article on singletons in C#并评估其他替代方案。

答案 1 :(得分:24)

懒惰版本:

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

    public static Singleton Instance => lazy.Value;
}

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

答案 2 :(得分:9)

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

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

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

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

当然,更容易推断“宽”锁而不是狭义,所以值得从它们开始广泛并根据需要进行优化,但在某些情况下我们可以从经验和熟悉程度中学习更狭窄的模式。

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

答案 3 :(得分:6)

原因是表现。如果instance != null(除了第一次以外总是如此),则不需要进行昂贵的lock:不必同时访问同时访问初始化单例的两个线程。

答案 4 :(得分:4)

几乎在所有情况下(即:除了第一个案例之外的所有案例),instance都不会为空。获取锁比使用简单检查更昂贵,因此在锁定之前检查instance的值是一个很好的免费优化。

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

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

答案 6 :(得分:0)

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

.Container {
  display: flex;
  align-items: center;
  box-sizing: border-box;
  width: 100%;
  height: 48px;
  margin: 0;
  padding: 0 15px;
}

.ProgressStepLabels {
  display: flex;
  align-items: center;
  box-sizing: border-box;
  width: 100%;
  height: 48px;
  margin: 0;
  padding: 0 15px;
}

.ProgressStep {
  flex-grow: 1;
  box-sizing: border-box;
  display: flex;
  align-items: center;
}

.ProgressDot {
  background-color: dodgerblue;
  max-width: 14px;
  height: 14px;
  flex-grow: 1;
  border-radius: 50%;
  border: 3px solid #fff;
  margin: 0 17px;
}

.ProgressBar {
  background-color: dodgerblue;
  flex-grow: 1;
  height: 2px;
}

.ProgressLabel {
  font-size: 14px;
  line-height: 17px;
  flex-grow: 1;
}

答案 7 :(得分:0)

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

int ids[] = {};
for(string str : strings)
{
     int id = 1;
     for(char c: str)
     {
        id *= c * list.nextPrime(); 
     }
    id ^ hash1;
    ids.push_back(id);
}

final_id = ids[0];
for(id : ids) final_id = (final_id * id) ^ hash2;

return final_id;

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

  

“以单例设计模式进行的渴望加载在其中没有任何过程   我们需要在初始化时初始化单例对象   应用程序启动而不是按需启动,并将其保留在内存中   将来使用。”

private static readonly Singleton singleInstance = new Singleton();

来自main:

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

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

答案 9 :(得分:-1)

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

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