《C#设计模式》【单例模式】

《C#设计模式》- 单例模式

深度解析六种单例模式实现方法,以及安全,性能分析

第一种:不是线程安全的

public  sealed  class  Singleton 
{ 
    private  static  Singleton instance = null ; 

    private  Singleton()
    { 
    } 

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

上述内容不是线程安全的。两个不同的线程都可以评估测试if (instance==null)并发现它为true,然后两个都创建实例,这违反了单例模式。请注意,实际上,在计算表达式之前可能已经创建了实例,但是内存模型不保证其他线程可以看到实例的新值,除非已经传递了合适的内存屏障(互斥锁) 

第二种 :简单线程安全

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

    Singleton()
    {
    }

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

此实现是线程安全的。线程取消对共享对象的锁定,然后在创建实例之前检查是否已创建实例。这会解决内存屏障问题(因为锁定确保在获取锁之后逻辑上发生所有读取,并且解锁确保在锁定释放之前逻辑上发生所有写入)并确保只有一个线程将创建实例(仅限于一次只能有一个线程可以在代码的那一部分中——当第二个线程进入它时,第一个线程将创建实例,因此表达式将计算为false)。不幸的是,每次请求实例时都会获得锁定,因此性能会受到影响。

请注意,不是typeof(Singleton)像这个实现的某些版本那样锁定,而是锁定一个私有的静态变量的值。锁定其他类可以访问和锁定的对象(例如类型)会导致性能问题甚至死锁。这是我通常的风格偏好——只要有可能,只锁定专门为锁定目的而创建的对象,或者为了特定目的(例如,等待/触发队列)而锁定的文档。通常这些对象应该是它们所使用的类的私有对象。这有助于使编写线程安全的应用程序变得更加容易。

第三种:双重检查锁定尝试线程安全

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

    Singleton()
    {
    }

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

该实现尝试是线程安全的,而不必每次都取出锁。不幸的是,该模式有四个缺点:

  • 它在Java中不起作用。这似乎是一个奇怪的事情,但是如果您需要Java中的单例模式,这是值得知道的,C#程序员也可能是Java程序员。Java内存模型无法确保构造函数在将新对象的引用分配给Instance之前完成。Java内存模型经历了1.5版本的重新改进,但是在没有volatile变量(如在C#中)的情况下,双重检查锁定仍然会被破坏。
  • 在没有任何内存障碍的情况下,ECMA CLI规范也打破了这一限制。有可能在.NET 2.0内存模型(比ECMA规范更强)下它是安全的,但我宁愿不依赖那些更强大的语义,特别是如果对安全性有任何疑问的话。使instance变量volatile变得有效,就像明确的内存屏障调用一样,尽管在后一种情况下,甚至专家也无法准确地就需要哪些屏障达成一致。我尽量避免专家对对错意见也不一致的情况!
  • 这很容易出错。该模式需要完全如上所述——任何重大变化都可能影响性能或正确性。
  • 它的性能仍然不如后续的实现。

第四种:饱汉式不使用锁且线程安全

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

    // 显式静态构造函数告诉C#编译器
    static Singleton()
    {
    }

    private Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            return instance;
        }
    }
}

C#中的静态构造函数仅在创建类的实例或引用静态成员时执行,并且每个AppDomain只执行一次。考虑到无论发生什么情况,都需要执行对新构造的类型的检查,这比在前面的示例中添加额外检查要快。然而,还有一些小缺陷:

  • 它并不像其他实现那样懒惰。特别是,如果您有Instance之外的静态成员,那么对这些成员的第一次引用将涉及到创建实例。这将在下一个实现中得到纠正。
  • 如果一个静态构造函数调用另一个静态构造函数,而另一个静态构造函数再次调用第一个构造函数,则会出现复杂情况。
  • 类型初始化器的懒惰性只有在.NET没有使用名为BeforeFieldInit的特殊标志标记类型时才能得到保证。不幸的是,C#编译器,将所有没有静态构造函数的类型。

第五种:饿汉式,锁且线程安全

public sealed class Singleton
{
    private Singleton()
    {
    }

    public static Singleton Instance { get { return Nested.instance; } }
        
    private class Nested
    {
        // 显式静态构造告诉C#编译器
        static Nested()
        {
        }

        internal static readonly Singleton instance = new Singleton();
    }
}

在这里,实例化是由对嵌套类的静态成员的第一次引用触发的,该引用只发生在Instance中。这意味着实现是完全懒惰的,但是具有前面实现的所有性能优势。请注意,尽管嵌套类可以访问封闭类的私有成员,但反之则不然,因此需要instance在此处为内部成员。不过,这不会引起任何其他问题,因为类本身是私有的。但是,为了使实例化变得懒惰,代码要稍微复杂一些。

第六种:使用.NET 4的 Lazy 类型【延迟加载对象】

public sealed class Singleton
{
    private static readonly Lazy<Singleton> lazy =
        new Lazy<Singleton>(() => new Singleton());
    
    public static Singleton Instance { get { return lazy.Value; } }

    private Singleton()
    {
    }
}

它很简单,而且性能很好。它还允许您检查是否已使用IsValueCreated 属性创建实例(如果需要的话)。

上面的代码隐式地将LazyThreadSafetyMode.ExecutionAndPublication用作Lazy<Singleton>的线程安全模式。根据您的要求,您可能希望尝试其他模式。

本文案例分析来源

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值