单例模式 (也叫单件模式) 的作用就是保证在整个应用程序的生命周期中, 任何一个时刻, 单例类的实例都只存在一个(当然也可以不存在)
下面来看单例模式的结构图:
从上面的类图中可以看出单例模式的特点:
在单例类中有一个构造函数 Singleton , 但是这个构造函数却是私有的
公开了一个 GetInstance()方法
通过上面的类图不难看出单例模式的特点, 从而也可以给出单例模式的定义:
单例模式保证一个类仅有一个实例, 同时这个类还必须提供一个访问该类的全局访问点
1. 最基本的单例:
- namespace Singleton
- {
- public class Singleton
- {
- // 定义一个私有的静态全局变量来保存该类的唯一实例
- private static Singleton singleton;
- /// <summary>
- /// 构造函数必须是私有的
- /// 这样在外部便无法使用 new 来创建该类的实例
- /// </summary>
- private Singleton()
- {
- }
- /// <summary>
- /// 定义一个全局访问点
- /// 设置为静态方法
- /// 则在类的外部便无需实例化就可以调用该方法
- /// </summary>
- /// <returns></returns>
- public static Singleton GetInstance()
- {
- // 这里可以保证只实例化一次
- // 即在第一次调用时实例化
- // 以后调用便不会再实例化
- if (singleton == null)
- {
- singleton = new Singleton();
- }
- return singleton;
- }
- }
- }
2. 线程安全单例
- namespace Singleton
- {
- public class Singleton
- {
- // 定义一个私有的静态全局变量来保存该类的唯一实例
- private static Singleton singleton;
- // 定义一个只读静态对象
- // 且这个对象是在程序运行时创建的
- private static readonly object syncObject = new object();
- /// <summary>
- /// 构造函数必须是私有的
- /// 这样在外部便无法使用 new 来创建该类的实例
- /// </summary>
- private Singleton()
- {
- }
- /// <summary>
- /// 定义一个全局访问点
- /// 设置为静态方法
- /// 则在类的外部便无需实例化就可以调用该方法
- /// </summary>
- /// <returns></returns>
- public static Singleton GetInstance()
- {
- // 这里可以保证只实例化一次
- // 即在第一次调用时实例化
- // 以后调用便不会再实例化
- // 第一重 singleton == null
- if (singleton == null)
- {
- lock (syncObject)
- {
- // 第二重 singleton == null
- if (singleton == null)
- {
- singleton = new Singleton();
- }
- }
- }
- return singleton;
- }
- }
- }
为何要使用双重检查锁定呢?
考虑这样一种情况, 就是有两个线程同时到达, 即同时调用 GetInstance(),
此时由于 singleton == null , 所以很明显, 两个线程都可以通过第一重的 singleton == null ,
进入第一重 if 语句后, 由于存在锁机制, 所以会有一个线程进入 lock 语句并进入第二重 singleton == null ,
而另外的一个线程则会在 lock 语句的外面等待
而当第一个线程执行完 new Singleton()语句后, 便会退出锁定区域, 此时, 第二个线程便可以进入 lock 语句块,
此时, 如果没有第二重 singleton == null 的话, 那么第二个线程还是可以调用 new Singleton()语句,
这样第二个线程也会创建一个 Singleton 实例, 这样也还是违背了单例模式的初衷的,
所以这里必须要使用双重检查锁定
其实在没有第一重 singleton == null 的情况下, 也是可以实现单例模式的, 那么为什么需要第一重 singleton == null 呢?
这里就涉及一个性能问题了, 因为对于单例模式的话, new Singleton()只需要执行一次就 OK 了,
而如果没有第一重 singleton == null 的话, 每一次有线程进入 GetInstance()时, 均会执行锁定操作来实现线程同步,
这是非常耗费性能的, 而如果我加上第一重 singleton == null 的话,
那么就只有在第一次, 也就是 singleton ==null 成立时的情况下执行一次锁定以实现线程同步,
而以后的话, 便只要直接返回 Singleton 实例就 OK 了而根本无需再进入 lock 语句块了, 这样就可以解决由线程同步带来的性能问题了
好, 关于多线程下单例模式的实现的介绍就到这里了, 但是, 关于单例模式的介绍还没完
下面将要介绍的是懒汉式单例和饿汉式单例
1. 懒汉式单例
何为懒汉式单例呢, 可以这样理解, 单例模式呢, 其在整个应用程序的生命周期中只存在一个实例,
懒汉式呢, 就是这个单例类的这个唯一实例是在第一次使用 GetInstance()时实例化的,
如果您不调用 GetInstance()的话, 这个实例是不会存在的, 即为 null
形象点说呢, 就是你不去动它的话, 它自己是不会实例化的, 所以可以称之为懒汉
其实呢, 我前面在介绍单例模式的这几个 Demo 中都是使用的懒汉式单例,
从前面的这个 GetInstance()中可以看出这个单例类的唯一实例是在第一次调用 GetInstance()时实例化的,
所以此为懒汉式单例
2. 饿汉式单例
上面介绍了饿汉式单例, 到这里来理解懒汉式单例的话, 就容易多了, 懒汉式单例由于人懒,
所以其自己是不会主动实例化单例类的唯一实例的, 而饿汉式的话, 则刚好相反,
其由于肚子饿了, 所以到处找东西吃, 人也变得主动了很多, 所以根本就不需要别人来催他实例化单例类的为一实例,
其自己就会主动实例化单例类的这个唯一类
在 C# 中, 可以用特殊的方式实现饿汉式单例, 即使用静态初始化来完成饿汉式单例模式
下面就来看一看饿汉式单例类
- namespace Singleton
- {
- public sealed class Singleton
- {
- private static readonly Singleton singleton = new Singleton();
- private Singleton()
- {
- }
- public static Singleton GetInstance()
- {
- return singleton;
- }
- }
- }
要先在这里提一下的是使用静态初始化的话, 无需显示地编写线程安全代码,
C# 与 CLR 会自动解决前面提到的懒汉式单例类时出现的多线程同步问题
上面的饿汉式单例类中可以看到, 当整个类被加载的时候, 就会自行初始化 singleton 这个静态只读变量
而非在第一次调用 GetInstance()时再来实例化单例类的唯一实例, 所以这就是一种饿汉式的单例类
好, 到这里, 就真正的把单例模式介绍完了, 在此呢再总结一下单例类需要注意的几点:
一单例模式是用来实现在整个程序中只有一个实例的
二单例类的构造函数必须为私有, 同时单例类必须提供一个全局访问点
三单例模式在多线程下的同步问题和性能问题的解决
四懒汉式和饿汉式单例类
五 C# 中使用静态初始化实现饿汉式单例类
来源: http://www.bubuko.com/infodetail-2496105.html