单例模式(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里了,只有一份。(这里不讨论方法中的临时变量,因为对于两者应该是一样的)
  • 因此静态方法和非静态方法,在调用速度上,静态方法速度一定会快点,因为非静态方法需要实例化,分配内存,但静态方法不用,但是这种速度上差异可以忽略不计。
  • 既然静态方法和实例化方式的区分是为了解决模式的问题,如果我们考虑不需要继承和多态的时候,就可以使用静态方法

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

原文地址:https://www.cnblogs.com/gyb333/p/SingletonPattern.html