zoukankan      html  css  js  c++  java
  • C# 之单例模式

    一、介绍:

    • 单例模式是软件工程中最著名的模式之一。从本质上来讲,单例是一个允许创建自身的单个实例的类,并且通常可以简单的访问该实例。
    • 单例不允许在创建实例时指定任何参数。
    • 单例通常要求他们是懒惰的创建,即直到第一次需要时才创建实例。

    单例的实现,有四个共同特征:

    • 单个构造函数,它是私有且无参数的。这可以防止其他类实例化它。
    • 类是密封的。严格来说,由于上述原因,这个不是必要的,但是可以帮助JIT进行更多的优化。
    • 一个静态变量,用于保存对单个已创建实例的引用。(如果有的话)
    • 公共静态意味着获取对单个已创建实例的引用,必要时创建一个实例。

      请注意:所有这些实现还使用公共静态属性Instance 作为访问实例的方法。在所有情况下,可以轻松将属性转换为方法,而不会影响线程安全和性能。

    二、单例的6个常见版本:

    • 第一个版本:不是线程安全的:(糟糕的代码,请勿使用)
      public sealed class Singleton1
        {
            private static Singleton1 instance = null;
    
            private Singleton1() { }
    
            public static Singleton1 Instance
            {
                get
                {
                    if (instance == null)
                    {
                        instance = new Singleton1();
                    }
    
                    return instance;
                }
            }
        }

    上面代码,不是线程安全的,两个不同线程都去竞争 if(instance == null) 时候,然后发现为true,会同时创建两个实例,这违反了单例原则。为了防止这种情况,我们想到了互斥锁,保证两个线程不会共同创建实例,请看第二个版本。

    • 第二个版本:简单的线程安全
      public sealed class Singleton2
        {
            private static Singleton2 instance = null;
            private static readonly object padlock = new object();
            Singleton2() { }
    
            public static Singleton2 Instance
            {
                get
                {
                    lock (padlock)//加锁,保证两个线程并发时,不会重复创建对象,但问题是每次线程运行还得检查锁(性能略差)
                    {
                        if (instance == null)
                        {
                            instance = new Singleton2();
                        }
    
                        return instance;
                    }
                }
            }
        }

     上述实现时线程安全的,加锁之后,防止多线程并发创建重复对象,但它有性能缺陷,因为每次使用Instance 调用实例时,都需要去判断锁,这样导致性能有影响,于是我们想到了第三个版本,双重判断加锁。

    • 第三个版本,使用双重检查锁定来尝试线程安全(糟糕的代码,请勿使用)
      public sealed class Singleton3
        {
            private static Singleton3 instance = null;
            private static readonly object padlock = new object();
    
            Singleton3() { }
    
            public static Singleton3 Instance
            {
                get
                {
                    if (instance == null)
                    {
                        lock (padlock)
                        {
                            if (instance == null)
                            {
                                instance = new Singleton3();
                            }
                        }
                    }
    
                    return instance;
                }
            }
        }

     该实现是线程安全的,不必每次都取出锁,即当第一次创建完实例后,下一次在调用实例,则不必再取出锁了。但该模式有四个缺点

    1. 它在Java中不起作用。
    2. 在没有任何内存障碍的情况下,ECMA CLI规范也打破了这一限制。
    3. 这很容易出错。该模式需要完全如上所述,任何重大变化都可能影响性能或正确性。
    4. 它的性能不如后续实现。
    •  第四个版本,不太懒,不使用锁且线程安全
      public sealed class Singleton4
        {
            private static readonly Singleton4 instance = new Singleton4();
    
            static Singleton4() { }//显示静态构造函数告诉C# 编译器
            private Singleton4() { }
    
            public static Singleton4 Instance
            {
                get
                {
                    return instance;
                }
            }
        }

    为什么说他不太懒惰?因为所谓懒惰是指我们要在调用实例时才去判断是否创建实例instance,然而这里,在类里先创建了实例。

    为什么说他是线程安全的?因为 C# 中静态构造函数仅在 创建类的实例 或 引用类的静态成员时执行,举个例子,例如我在调用Instance 这个实例时,会执行第一句  instance = new Singleton4(),当我第二次再调用Instance 使用静态实例时,它则不会重复创建实例,所以它是线程安全的。

    • 第五个版本,完全懒惰实例化
        public sealed class Singleton5
        {
            private Singleton5() { }
    
            public static Singleton5 Instance { get { return Nested.instance; } }//完全懒惰的,因为必须在执行这个Instance调用时,才会执行嵌套类的创建单例语句
    
            private class Nested
            {
                static Nested() { }
    
                internal static readonly Singleton5 instance = new Singleton5();//此处要为internal的,外部要访问,不能是私有的。此处在外部调用时,需要创建单例对象时创建,实现完全懒惰
            }
    
        }

     上述代码是懒惰的,因为在封闭类中调用子类的instance时才去创建实例,而不是像第四个版本那样先创建了实例。请注意,尽管嵌套类可以访问封闭类中的私有成员,但是封闭类不能访问嵌套类内层,所以这里的嵌套类实例需要使用internal 关键字声明。

    • 第六个版本:使用.NET 4 的Lazy 类型。
        public sealed class Singleton6
        {
            private static readonly Lazy<Singleton6> lazy = new Lazy<Singleton6>(() => new Singleton6());
    
            public static Singleton6 Instance { get { return lazy.Value; } }
    
            private Singleton6() { }
        }

     这里使用.NET4 或者更高版本,可以使用System.Lazy 这个类型声明懒惰的,线程安全的单例,同时他的性能非常好。

    三、懒惰与性能

    在许多情况下,其实不需要完全懒惰,除非您的初始化做了一些特别耗时的事情,或者其他地方产生了一些副作用,否则最好忽略上面所示的显示静态构造函数。这可以提高性能,因为它允许JIT编译器进行一次检查(例如在方法的开头)以确保类型已经初始化,然后从那时开始设定它。如果在相对紧密的循环中引用单例实例,则会产生(相对)显著的性能差异。您应该决定是否需要完全延迟实例化,并在类中适当地记录此决策。

    参考原文:https://www.cnblogs.com/leolion/p/10241822.html

  • 相关阅读:
    Vue 登录/登出以及JWT认证
    Vue render函数 函数时组件 jsx
    Mock简明文档
    Vue axios封装 实现请求响应拦截
    Promise场景实例之图片加载
    第一章 HTML5基础
    python if的选择结构与循环
    python 字典
    CentOS 6.5设置静态IP教程 并且可以ping通
    centos 主从复制
  • 原文地址:https://www.cnblogs.com/vpersie2008/p/12272696.html
Copyright © 2011-2022 走看看