关于C#中实现单例模式

单例模式是设计模式中非常经典的模式,用于在程序全局下只创建一个实例访问,常用于需要创建唯一性的地方,在C#中也有很多方法可以实现单例模式,在这里记录一下,但是兼顾优雅和性能的创建方法并不多。

  • 版本一 懒汉式 原始简易版 非线程安全 不可用

在多线程下,可能多个线程都判断instance == null为true,创建多个实例

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

    private  Singleton()
    { 
    } 

    public  static  Singleton Instance 
    { 
        get 
        { 
            if (instance == null)
            { 
                instance =  new  Singleton(); 
            } 
            return  instance; 
        } 
    } 
}
  • 版本二 懒汉式 简单的线程安全 不推荐使用

主要问题在于,每次获取实例都需要加锁,这会带来性能损耗

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;
            }
        }
    }
}
  • 版本三 懒汉式 使用双重检查锁定尝试线程安全 可用 不推荐

这是版本二的改良版,避免每次获取实例都加锁,只有当在创建实例时才进行加锁,但是依然存在一些缺点:

  1. 它在Java中不起作用,在C#中习惯这么写到了java中可能导致错误。Java内存模型无法确保构造函数在将新对象的引用分配给Instance之前完成。Java内存模型经历了1.5版本的重新改进,但是在没有volatile变量(如在C#中)的情况下,双重检查锁定仍然会被破坏。
  2. 性能不是最优的
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;
        }
    }
}
  • 版本四 不太懒,不使用锁且线程安全 可用

C#中的静态构造函数仅在创建类的实例或引用静态成员时执行,并且每个AppDomain只执行一次。

但是如果有Instance之外的静态成员,那么对这些成员的第一次引用将创建实例,所以并不是完全懒惰。

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

    // 显式静态构造函数告诉C#编译器
    // 不要将类型标记为BeforeFieldInit
    static Singleton()
    {
    }

    private Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            return instance;
        }
    }
}
  • 版本五 完全懒惰 可用

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

public sealed class Singleton
{
    private Singleton()
    {
    }

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

        internal static readonly Singleton instance = new Singleton();
    }
}
  • 版本六 使用 Lazy 类型,可以很简单的实现懒加载,而且提供线程安全选项,推荐

线程安全性

默认情况下,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()
    {
    }
}

Lazy有多个构造函数,Lazy()默认为多个线程访问,即线程安全,也可使用Lazy(Func, Boolean)构造函数,并将 isThreadSafe 设置为 falseLazy(Boolean)。

此外,Lazy(Func, LazyThreadSafetyMode)与Lazy(Func, Boolean)是等效的,也可以使用其他模式使用,不过写法麻烦一些,上面示例的版本已经够用了。

posted @ 2021-01-07 11:45  Yeah的第七章  阅读(293)  评论(0编辑  收藏  举报