• 单例模式(Singleton Pattern) 创建型模式


    述:

      Singleton模式要求一个类有且仅有一个实例,并且提供了一个全局的访问点。如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?客户程序在调用某一个类时,它是不会考虑这个类是否只能有一个实例等问题的,所以,这应该是类设计者的责任,而不是类使用者的责任。

      从另一个角度来说,Singleton模式其实也是一种职责型模式。因为我们创建了一个对象,这个对象扮演了独一无二的角色,在这个单独的对象实例中,它集中了它所属类的所有权力,同时它也肩负了行使这种权力的职责!

    意图:

      保证一个类仅有一个实例,并提供一个访问它的全局访问点。

    UML图:

     

     实质:

    • 有一个私有的无参构造函数,这可以防止其他类实例化它,而且单例类也不应该被继承,如果单例类允许继承那么每个子类都可以创建实例,这就违背了Singleton模式“唯一实例”的初衷。
    • 单例类被定义为sealed,该类不应该被继承,该类定义成不允许派生,但没有要求一定要这样定义。
    • 一个静态的变量用来保存单实例的引用。
    • 一个公有的静态方法用来获取单实例的引用,如果实例为null即创建一个。

    线程不安全:

    View Code
     public sealed class Singleton
    
        {
    
            private static Singleton instance = null;   //惰性实例化
    
            private Singleton(){    }
    
            public static Singleton Instance
    
            {
    
        get { return instance ?? (instance = new Singleton()); } //对于线程来说并不是安全
    
            }
    
    }

     

     锁定线程安全:

    View Code
    public sealed class Singleton
    
        {
    
            private static Singleton instance = null;
    
            private static readonly object SynObject = new object();
    
            private Singleton(){    }
    
            public static Singleton Instance
    
            {
    
                get
    
                {
    
                        lock (SynObject)    //对象加锁 增加了额外的开销,损失了性能。
    
                        {
    
                            if (null == instance) instance = new Singleton();
    
                        }
    
                    return instance;
    
                }
    
            }
    
        }


    双重锁定线程安全(有属性多线程下该属性也可能不安全):

    View Code
    public sealed class Singleton
    
        {
    
            private static Singleton instance = null;
    
            private static readonly object SynObject = new object();
    
            private Singleton(){    }
    
            public static Singleton Instance
    
            {
    
                get
    
                {
    
                    if (null == instance)
    
                    {
    
                        lock (SynObject)    //对象加锁 增加了额外的开销,损失了性能。
    
                        {
    
                            if (null == instance) instance = new Singleton();
    
                        }
    
                    }
    
                    return instance;
    
                }
    
            }
    
        }

     

     静态初始化(有属性,多线程下该属性也可能不安全):

    View Code
      public sealed class Singleton
    
        {
    
            private static readonly Singleton instance = new Singleton();  //只能在静态初始化期间或在类构造函数中分配
    
            private Singleton(){       }
    
            public static Singleton Instance
    
            {
    
                get{    return instance; }
    
            }
    
        }

     

     

    延迟初始化(有私有属性多线程下该属性也可能不安全):

    View Code
     public sealed class Singleton
    
        {
    
            private Singleton(){    }
    
            public static Singleton Instance { get { return Nested.instance; } }
    
     
    
            private class Nested        //内部类
    
            {
    
                internal static readonly Singleton instance = new Singleton();
    
            }
    
    }

     

     Lazy<T> type

     

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

     

    优点

    • 在内存中只有一个对象,节省内存空间。
    • 避免频繁的创建销毁对象,可以提高性能。
    • 避免对共享资源的多重占用。
    • 可以全局访问。

    缺点

    • 单例模式因为提供了一个全局的访问点,你可以在程序的任何地方轻而易取地访问到,这本身就是一种高耦合的设计。一旦单例改变以后,其它模板都需要修改。
    • 单例模式使得对象变成了全局的了。全局变量是非常邪恶的,要尽量不要使用。
    • 如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。(我不这么认为, 静态的字段持有对象的引用, 因为静态字段属于类型,而类型的地址在CLR看来是加载了类型后,就不会改变的。因为单例是通过类型访问的静态字段对应的实例会一直在内存中,不会被GC清除的(原因是静态的属性变量不会被GC清除)

    使用场景

    • 需要频繁实例化然后销毁的对象。
    • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
    • 有状态的工具类对象。
    • 频繁访问数据库或文件的对象。
    • 以及其他我没用过的所有要求只有一个对象的场景。
    • 不要使用单例模式存取全局变量。这违背了单例模式的用意,最好放到对应类的静态成员中。
    • 不要将数据库连接做成单例,因为一个系统可能会与数据库有多个连接,并且在有连接池的情况下,应当尽可能及时释放连接。Singleton模式由于使用静态成员存储类实例,所以可能会造成资源无法及时释放,带来问题。
    • 只能使用单例类提供的方法得到单例对象,不要使用反射,否则将会实例化一个新对象。

    陷阱(除上面锁定线程安全实现外)

    • 单例类中尽量不要含有非单例类的实例作为私有属性(容器类(多线程安全)除外),一定要有类的实例作为私有属性的时候,重新审视这个作为私有属性的类,是不是也应该设计成单例类;或者保证对它的初始化赋值限制在构造函数内(因为只实例化一次,而且加锁线程安全)。不然会导致多线程访问单例类的私有属性时,掉入访问互斥资源陷阱。
    • 单例陷阱的传递:当含有对象作为单例类的私有属性时,陷阱不仅会出现在该类本身,还会传递到私有对象所在的类中。

    单例模式、静态类、静态方法

    • 托管堆:对于32位的应用程序来说,应用程序完成进程初始化后,CLR将在进程的可用地址空间分配一块保留的地址空间,它是进程(每个进程可使用4GB)中可用地址空间上的一块内存区域,但并不对应任何物理内存,这块地址空间即是托管堆。
    • 托管堆:垃圾回收堆(GC Heap)和加载堆(Loader Heap),GC Heap用于存储对象实例,受GC管理;Loader Heap又分为High-Frequency Heap、Low-Frequency Heap和Stub Heap,不同的堆上又存储不同的信息。
    • Loader Heap最重要的信息就是元数据相关的信息,也就是Type对象,每个Type在Loader Heap上体现为一个Method Table(方法表),而Method Table中则记录了存储的元数据信息,例如基类型、静态字段、实现的接口、所有的方法等等。Loader Heap不受GC控制,其生命周期为从创建到AppDomain卸载。(摘自《你必须知道的.Net》)
    • 静态类的语义是全局唯一代码段,而单例的语义是全局唯一对象实例。单例类就是采用单例模式的类,单例类强调用它构建的实例是唯一的。(基于对象的唯一)
    • 静态类就是所有的属性和方法都是静态的类,强调该类的所有属性和成员都是基于类的,而不是基于某一个对象的。(基于类的唯一)
    • 静态方法和非静态方法,在内存里其实都放在Method Table里了,在一个类第一次被加载的时候,它会在Loader Heap里把静态方法,非静态方法都写入Method Table中,而且Loader Heap不受GC控制,所以一旦加载,GC就不会回收,直到AppDomain卸载。静态方法和非静态方法,他们都是在第一次加载后就常驻内存,所以方法本身在内存里,没有什么区别。

    静态方法和非静态方法的区别?

    • 在内存中的区别是,非静态方法在调用时需要创建实例对象,因为属性的值对于每个对象都各不相同,因此在new一个实例时,会把这个实例属性在GC Heap里拷贝一份,同时这个new出来的对象放在堆栈上,堆栈指针指向了刚才拷贝的那一份实例的内存地址上。而在调用静态方法则不需要,因为静态方法里面的静态字段,就是保存在Method Table里了,只有一份。(这里不讨论方法中的临时变量,因为对于两者应该是一样的)
    • 因此静态方法和非静态方法,在调用速度上,静态方法速度一定会快点,因为非静态方法需要实例化,分配内存,但静态方法不用,但是这种速度上差异可以忽略不计。
    • 既然静态方法和实例化方式的区分是为了解决模式的问题,如果我们考虑不需要继承和多态的时候,就可以使用静态方法

    总结:以上纯属个人的理解,对于有些地方觉得还是理解不是很深,有不足之处和错误的地方希望大家帮我指出。谢谢

  • 相关阅读:
    联想顶级移动工作站ThinkPad P16发布,配置拉满,起售价1979美元
    男士美容,这些方面你做了没? 生活至上,美容至尚!
    一起学习手撕包菜如何做 生活至上,美容至尚!
    男士美容护肤家庭自用法 生活至上,美容至尚!
    “皮蛋瘦肉粥”2种详细做法[图] 生活至上,美容至尚!
    简单实用的男士护肤法 生活至上,美容至尚!
    养颜排毒女人注意的几个方面 生活至上,美容至尚!
    八种不要钱的男士护肤方法 生活至上,美容至尚!
    ArcGIS拓扑:检查的流程,以面重叠检查为例
    ERROR 010067: Error in executing grid expression.
  • 原文地址:https://www.cnblogs.com/gyb333/p/SingletonPattern.html
Copyright © 2020-2023  润新知