原本我本身写了一篇,就在发布的时候,博客园也不知道怎么抽抽了,尼玛,我写的那么久的东西整个都没有了,就留了一句“什么是单例模式呢”,汝妹!不开森!!!这件事情告诉我,之后要如今Word活着其余编辑器上编辑好再复制到博文,否则就是白费了一腔心血。html
如下文章转载自:单例模式(Singleton),例子我本身也写了,但与这篇文章也是差很少同样的。由于上述缘由,不想再写一遍了,就转载了这一篇我以为看起来轻松好理解的文章。安全
简单说来,单例模式(也叫单件模式)的做用就是保证在整个应用程序的生命周期中,任何一个时刻,单例类的实例都只存在一个(固然也能够不存在)。多线程
下面来看单例模式的结构图(图太简单了)编辑器
从上面的类图中能够看出,在单例类中有一个构造函数 Singleton ,可是这个构造函数倒是私有的(前面是“ - ”符号),而后在里面还公开了一个 GetInstance()方法,函数
经过上面的类图不难看出单例模式的特色,从而也能够给出单例模式的定义post
单例模式保证一个类仅有一个实例,同时这个类还必须提供一个访问该类的全局访问点。性能
先来将 Singleton 写出来再说编码
public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton GetInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
调用:url
class Program { static void Main(string[] args) { Singleton a = Singleton.GetInstance(); Singleton b = Singleton.GetInstance(); if (a.Equals(b)) { System.Console.WriteLine("实例确实相同"); System.Console.ReadLine(); } } }
运行结果为spa
从上面的结果能够看出来,尽管我两次访问了 GetInstance(),可是我访问的只是同一个实例,换句话来讲,上面的代码中,因为构造函数被设置为 private 了,因此您没法再在 Singleton 类的外部使用 new 来实例化一个实例,您只能经过访问 GetInstance()来访问 Singleton 类,
GetInstance()经过以下方式保证该 Singleton 只存在一个实例:首先这个 Singleton 类会在在第一次调用 GetInstance()时建立一个实例,并将这个实例的引用封装在自身类中,而后之后调用 GetInstance()时就会判断这个 Singleton 是否存在一个实例了,若是存在,则不会再建立实例。而是调用之前生成的类的实例,这样下来,整个应用程序中便就只存在一个实例了。
从这里再来总结单例模式的特色:
首先,单例模式使类在程序生命周期的任什么时候刻都只有一个实例,
而后,单例的构造函数是私有的,外部程序若是想要访问这个单例类的话,
必须经过 GetInstance()来请求(注意是请求)获得这个单例类的实例。
有的时候,老是容易把全局变量和单例模式给弄混了,下面就剖析一下全局变量和单例模式相比的缺点
首先,全局变量呢就是对一个对象的静态引用,全局变量确实能够提供单例模式实现的全局访问这个功能,
可是,它并不能保证您的应用程序中只有一个实例,同时,在编码规范中,也明确指出,
应该要少用全局变量,由于过多的使用全局变量,会形成代码难读,
还有就是全局变量并不能实现继承(虽然单例模式在继承上也不能很好的处理,可是仍是能够实现继承的)
而单例模式的话,其在类中保存了它的惟一实例,这个类,它能够保证只能建立一个实例,
同时,它还提供了一个访问该惟一实例的全局访问点。
下面来看一种状况(这里先假设个人应用程序是多线程应用程序),同时仍是之前面的 Demo 来作为说明,
若是在一开始调用 GetInstance()时,是由两个线程同时调用的(这种状况是很常见的),注意是同时,(或者是一个线程进入 if 判断语句后但尚未实例化 Singleton 时,第二个线程到达,此时 singleton 仍是为 null),两个线程均会进入 GetInstance(),然后因为是第一次调用 GetInstance(),存储在 Singleton 中的静态变量 singleton== null ,两个线程都可经过 if 语句的条件判断,会建立两个实例,很显然,这便违法了单例模式的初衷了,
那么如何解决上面出现的这个问题(即多线程下使用单例模式时有可能会建立多个实例这一现象)呢?
其实,这个是很好解决的,
因为上面出现的问题中涉及到多个线程同时访问这个 GetInstance(),那么您能够先将一个线程锁定,而后等这个线程完成之后,再让其余的线程访问 GetInstance()中的 if 段语句,
好比,有两个线程同时到达若是 singleton != null 的话,那么上面提到的问题是不会存在的,由于已经存在这个实例了,全部的线程都没法进入 if 语句块,
也就是全部的线程都没法调用语句 new Singleton()了,这样仍是能够保证应用程序生命周期中的实例只存在一个,
可是若是此时的 singleton == null 的话,那么意味着这两个线程都是能够进入这个 if 语句块的,那么就有可能出现上面出现的单例模式中有多个实例的问题,
此时,我可让一个线程先进入 if 语句块,而后我在外面对这个 if 语句块加锁,对第二个线程呢,因为 if 语句进行了加锁处理,因此这个进程就没法进入 if 语句块而处于阻塞状态,
当进入了 if 语句块的线程完成 new Singleton()后,这个线程便会退出 if 语句块,此时,第二个线程就从阻塞状态中恢复,即就能够访问 if 语句块了,可是因为前面的那个线程已近建立了 Singleton 的实例,因此 singleton != null ,此时,第二个线程便没法经过 if 语句的判断条件了,即没法进入 if 语句块了,这样便保证了整个生命周期中只存在一个实例,也就是只有第一个线程建立了 Singleton 实例,第二个线程则没法建立实例。
下面就来从新改进前面 Demo 中的 Singleton 类,使其在多线程的环境下也能够实现单例模式的功能。
public class Singleton { // 定义一个static的全局变量来保存该类惟一实例 private static Singleton singleton; //该对象在程序运行时建立 private static readonly object syncObject = new object(); // private, 确保外部调用时不能用new来建立实例 private Singleton() { } // 定义一个static的全局访问点,确保外部无需实例化便可调用 public static Singleton GetInstance() { // 保证只在第一次调用时实例化一次 if (singleton == null) { lock (syncObject)//锁定 { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
上面的就是改进后的代码,能够看到在类中有定义了一个静态的只读对象 syncObject,为什么还要建立一个 syncObject 静态只读对象呢?
因为提供给 lock 关键字的参数必须为基于引用类型的对象,该对象用来定义锁的范围,因此这个引用类型的对象不能为 null ,而开始的时候,singleton 为 null,没法实现加锁,必需要再建立一个对象即 syncObject 来定义加锁的范围。
为何要在 if 语句中使用两次判断 singleton == null ?这里涉及到一个名词 Double-Check Locking ,也就是双重检查锁定,为什么要使用双重检查锁定呢?
考虑这样一种状况,就是有两个线程同时到达,即同时调用 GetInstance(),此时因为 singleton == null ,因此很明显,两个线程均可以经过第一重的 singleton == null ,进入第一重 if 语句后,因为存在锁机制,因此会有一个线程进入 lock 语句并进入第二重 singleton == null ,而另外的一个线程则会在 lock 语句的外面等待。当第一个线程执行完 new Singleton()语句后,便会退出锁定区域,此时,第二个线程即可以进入 lock 语句块,此时,若是没有第二重 singleton == null 的话,那么第二个线程仍是能够调用 new Singleton()语句,这样第二个线程也会建立一个 Singleton 实例,这样也仍是违背了单例模式的初衷的,因此这里必需要使用双重检查锁定。
细心的朋友必定会发现,若是我去掉第一重 singleton == null ,程序仍是能够在多线程下无缺的运行的,考虑在没有第一重 singleton == null 的状况下,当有两个线程同时到达,此时,因为 lock 机制的存在,第一个线程会进入 lock 语句块,而且能够顺利执行 new Singleton(),当第一个线程退出 lock 语句块时, singleton 这个静态变量已不为 null 了,因此当第二个线程进入 lock 时,仍是会被第二重 singleton == null 挡在外面,而没法执行 new Singleton(),因此在没有第一重 singleton == null 的状况下,也是能够实现单例模式的?那么为何须要第一重 singleton == null 呢?
这里就涉及一个性能问题了,由于对于单例模式的话,new Singleton()只须要执行一次就 OK 了,而若是没有第一重 singleton == null 的话,每一次有线程进入 GetInstance()时,均会执行锁定操做来实现线程同步,这是很是耗费性能的,而若是我加上第一重 singleton == null 的话,那么就只有在第一次,也就是 singleton ==null 成立时的状况下执行一次锁定以实现线程同步,而之后的话,便只要直接返回 Singleton 实例就 OK 了而根本无需再进入 lock 语句块了,这样就能够解决由线程同步带来的性能问题了。
好,关于多线程下单例模式的实现的介绍就到这里了,可是,关于单例模式的介绍还没完。
下面将要介绍的是懒汉式单例和饿汉式单例
懒汉式单例
何为懒汉式单例呢,能够这样理解,懒汉式呢,就是这个单例类的这个惟一实例是在第一次使用 GetInstance()时实例化的,若是您不调用 GetInstance()的话,这个实例是不会存在的,即为 null 。形象点说呢,就是你不去动它的话,它本身是不会实例化的,因此能够称之为懒汉。其实呢,我前面在介绍单例模式的这几个 Demo 中都是使用的懒汉式单例,
看下面的 GetInstance()方法就明白了:
public static Singleton GetInstance() { // 保证只在第一次调用时实例化一次 if (singleton == null) { lock (syncObject)//锁定 { if (singleton == null) { singleton = new Singleton(); } } } return singleton; }
从上面的这个 GetInstance()中能够看出这个单例类的惟一实例是在第一次调用 GetInstance()时实例化的,因此此为懒汉式单例。
饿汉式单例
懒汉式单例因为人懒,因此其本身是不会主动实例化单例类的惟一实例的,而饿汉式的话,则恰好相反,其因为肚子饿了,因此处处找东西吃,人也变得主动了不少,因此根本就不须要别人来催他实例化单例类的为一实例,其本身就会主动实例化单例类的这个惟一类。在 C# 中,能够用特殊的方式实现饿汉式单例,即便用静态初始化来完成饿汉式单例模式
public sealed class Singleton { private static readonly Singleton singleton = new Singleton(); private Singleton() { } public static Singleton GetInstance() { return singleton; } }
要先在这里提一下的是使用静态初始化的话,无需显示地编写线程安全代码,C# 与 CLR 会自动解决前面提到的懒汉式单例类时出现的多线程同步问题。上面的饿汉式单例类中能够看到,当整个类被加载的时候,就会自行初始化 singleton 这个静态只读变量。而非在第一次调用 GetInstance()时再来实例化单例类的惟一实例,因此这就是一种饿汉式的单例类。
好,到这里,就真正的把单例模式介绍完了,在此呢再总结一下单例类须要注意的几点:
1、单例模式是用来实如今整个程序中只有一个实例的。
2、单例类的构造函数必须为私有,同时单例类必须提供一个全局访问点。
3、单例模式在多线程下的同步问题和性能问题的解决。
4、懒汉式和饿汉式单例类。
5、C# 中使用静态初始化实现饿汉式单例类。