线程安全的 C# 单例模式

     2023-02-16     212

关键词:

【中文标题】线程安全的 C# 单例模式【英文标题】:Thread Safe C# Singleton Pattern 【发布时间】:2012-09-01 05:41:18 【问题描述】:

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

执行锁定是否昂贵?

【问题讨论】:

顺便说一句,Jon Skeet 有一篇关于单例线程安全的精彩文章:csharpindepth.com/Articles/General/Singleton.aspx 懒惰的静态初始化会更好...... 我这里还有其他的例子和解释:csharpindepth.com/Articles/General/Singleton.aspx 与 Java 世界完全相同的问题 here。 【参考方案1】:

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

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

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

上述实现对于大多数情况来说已经足够好了,但此时最好阅读Jon Skeet's article on singletons in C#,它还评估了其他替代方案。

【讨论】:

感谢您提供有用链接的信息回复。非常感谢。 双重检查锁定 - 链接不再起作用。 对不起,我指的是另一个。 @ElMac:Skeet 的网站在 ATM 上停机,将在适当的时候恢复。我会记住这一点,并确保链接在出现时仍然有效,谢谢。 从 .NET 4.0 开始,Lazy<T> 完美地完成了这项工作。【参考方案2】:

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

【讨论】:

@ttugates,你是对的,谢谢。使用惰性对象的值工厂回调更新代码。【参考方案3】:

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

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

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

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

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

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

【讨论】:

我真的很喜欢你在这里的想法。这是一个很好的方式来看待它。我希望我能接受两个答案或 +5 这个答案,非常感谢 当需要考虑性能时,一个很重要的结果是可能同时命中的共享结构和命中的共享结构之间的区别.有时我们并不期望这种行为经常发生,但它可能会发生,所以我们需要锁定(锁定一次失败就可以毁掉一切)。其他时候,我们知道很多线程确实会同时访问相同的对象。然而其他时候,我们并不期望会有很多并发,但我们错了。当您需要提高性能时,那些具有大量并发性的优先。 在您的选择中,volatile 不是必需的,但它应该是 readonly。见***.com/q/12159698/428724。【参考方案4】:

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

【讨论】:

【参考方案5】:

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

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

【讨论】:

【参考方案6】:

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;
            
        
    

【讨论】:

不是让实例变量易失,做同样的事情吗?【参考方案7】:

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

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;
        
    

【讨论】:

【参考方案8】:

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

public sealed class Singleton

    private static readonly Singleton instance = new Singleton();

    private Singleton()  

    public static Singleton Instance()
    
        return instance;
    

【讨论】:

【参考方案9】:

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

private static readonly Singleton singleInstance = new Singleton();

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

“单例设计模式中的 Eager loading 不是一个过程 我们需要在这个时候初始化单例对象 应用程序启动而不是按需启动并将其保存在内存中 以后会用到。”

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

【讨论】:

不错的选择,但没有回答关于问题中提到的具体实现中的锁定检查的问题 不是直接使用,但可以用作替代“线程安全 C# 单例模式”。【参考方案10】:

抗反射单例模式:

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

【讨论】:

再说单例模式的线程安全问题

今天和同事聊起了单例模式的线程安全,我说如果不做任何措施,单例模式在多线程下是不安全的,得到的“单例”实际上并不是单例。但是为什么不是单例呢?由此我上网查了一下,在使用单例模式时,一定要注意线程... 查看详情

单例模式线程安全问题

//饥汉模式 本身是一种线程安全的方法,再类创建的时候,对象就生成了,所以不存在线程同步的问题缺点:项目启动比较慢publicclassSingleton{  privatestaticfinalSingletoninstance=newSingleton();  privateSingleton(){   }   publicstatic... 查看详情

单例模式的线程安全性

...式”是在你真正用到的时候才去建这个单例对象,所以是线程不安全的懒汉式如果在创建实例对象时 查看详情

1spring单例模式与线程安全

...候,考虑到Spring中的bean默认是单例模式的,那么当多个线程调用同一个bean的时候就会存在线程安全问题。如果是Spring中bean的创建模式为非单例的,也就不存在这样的问题了。二、Spring 单例模式与线程安全Spring 框架里的... 查看详情

高并发下线程安全的单例模式

...现方式,你是否都了解呢?高并发下如何保证单例模式的线程安全性呢?如何保证序列化后的单例对象在反序列化后任然是单例的呢?这些问题在看了本文之后都会一一的告诉你答案,赶快来阅读吧!什么是单例模式?在文 查看详情

controller是单例模式的吗?如何保证线程安全?

参考技术AController是单例模式的吗?如何保证线程安全?答:Controller是单例的,也就是说并发请求调用Controller生成的是同一个对象。从线程安全的角度来说,这些线程共享Controller的实例对象。接下来我们说一下线程安全的问题... 查看详情

多线程阻塞队列定时器线程安全的单例模式的原理及实现(代码片段)

文章目录1.线程安全版本的单例模式1.1单例模式介绍1.2实现线程安全版本的懒汉模式2.阻塞队列2.1阻塞队列介绍2.2标准库中的阻塞队列2.3实现阻塞队列2.4生产者消费者模型3.定时器3.1定时器介绍3.2标准库中的定时器3.3实现定时器1.... 查看详情

多线程阻塞队列定时器线程安全的单例模式的原理及实现(代码片段)

文章目录1.线程安全版本的单例模式1.1单例模式介绍1.2实现线程安全版本的懒汉模式2.阻塞队列2.1阻塞队列介绍2.2标准库中的阻塞队列2.3实现阻塞队列2.4生产者消费者模型3.定时器3.1定时器介绍3.2标准库中的定时器3.3实现定时器1.... 查看详情

单例模式与线程安全问题浅析

...看到到Struts1与Struts2的比較。说Struts1的控制器是单例的,线程不安全的;Struts2的多例的,不存在线程不安全的问题。之后又想到了之前自己用过的HttpHandler。。。这些类。好像单例的线程安全问题确实是随处可见的。可是仅仅是... 查看详情

多线程实现单例模式(饿汉懒汉)实现线程安全的单例模式(双重效验锁)(代码片段)

...比饿汉模式好。主要因为懒汉模式的效率更高1.饿汉模式(线程安全)//饿汉模 查看详情

实现线程安全的单例模式

一、双检查锁机制packagesingleton;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;/***双检查锁机制--单例模式*Createdbydaizengjieon2017/8/29.*/publicclassMySingleton{privatestaticfinalLoggerlogger=LoggerFactory.getL 查看详情

线程安全的单件模式(单例模式)

1.定义:     某一个类只有一个实例,并且这个实例是在类内部进行实例化,并向整个系统提供该实例。2.单例模式的通用代码: public sealed class Singleton   {      &n... 查看详情

单例模式

... 单例模式的实现有多种方式,如下所示: 1、懒汉式,线程不安全 是否Lazy初始化:是 是否多线程安全:否 实现难度:易 描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁sync... 查看详情

设计模式---单例模式(代码片段)

...静态常量的优缺点静态代码块静态代码块的优缺点懒汉式线程不安全的写法优缺点线程安全,同步锁---效率低,不推荐优缺点线程安全,同步代码块---无法解决线程安全问题,不推荐优缺点双重检查---解决线程安... 查看详情

线程安全的单例模式

1.全局变量的缺点:必须在程序一开始就创建好对象,如果程序在这次的执行过程中又一直没用到它,就非常耗费资源。 2. 经典的单例模式实现:Java代码publicclassSingleton{//用一个静态变量来记录Singleton类的唯一实例privates... 查看详情

线程安全的单例模式(代码片段)

饿汉模式1publicclassSingle23privatestaticSingleinstance=newSingle();45privateSingle()6System.out.println("Single:"+System.nanoTime());789publicstaticSinglegetInstance()10returninstance;1112  查看详情

java基础——线程安全的单例模式懒汉式

packagesavesingleton;/*使用同步将单例模式中的懒汉式改写成线程安全的@authorzsben@create2020-01-0322:22*/classBank{privateBank(){}privatestaticBankinstance=null;/*publicstaticsynchronizedBankgetInstance(){if(instance==null){ 查看详情

设计模式-创建型模式_7种单例模式实现(代码片段)

...创建型模式概述Case7种单例模式实现静态类使⽤懒汉模式(线程不安全)懒汉模式(线程安全)饿汉模式(线程安全)使⽤类的内部类(线程安全)双重锁校验(线程安全)CAS「AtomicReference」(线程安全)EffectiveJava作者推荐的枚举单例(线程安全)... 查看详情