单例模式: 定义 一个类有且仅有一个实例,并且自行实例化向整个系统提供。
《来自百科》 这些 概念和要点 是 了解 单例模式的 一些基本概念
C# 单例实现要点
Singleton模式是限制而不是改进类的创建。
Singleton类中的实例构造器可以设置为Protected以允许子类派生。
Singleton模式一般不要支持Icloneable接口,因为这可能导致多个对象实例,与Singleton模式的初衷违背。
Singleton模式一般不要支持序列化,这也有可能导致多个对象实例,这也与Singleton模式的初衷违背。
Singleton只考虑了对象创建的管理,没有考虑到销毁的管理,就支持垃圾回收的平台和对象的开销来讲,我们一般没必要对其销毁进行特殊的管理。
理解和扩展Singleton模式的核心是“如何控制用户使用new对一个类的构造器的任意调用”。
可以很简单的修改一个Singleton,使它有少数几个实例,这样做是允许的而且是有意义的
优点
实例控制:Singleton 会阻止其他对象实例化其自己的 Singleton 对象的副本,从而确保所有对象都访问唯一实例
灵活性:因为类控制了实例化过程,所以类可以更加灵活修改实例化过程
缺点
开销:虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题,上面的五种实现方式中已经说过了。
可能的开发混淆:使用 singleton 对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用 new 关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
对象的生存期:Singleton 不能解决删除单个对象的问题。在提供 内存管理 的语言中(例如基于 .NET Framework 的语言),只有 Singleton 类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除
对象实例,但这样会导致 Singleton 类中出现悬浮引用。
适用性
当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。
private static Singleton instance; /// <summary> /// 把构造函数 声明 为 私有,这样就 阻止了 外界 对 这个类的new 操作 /// </summary> private Singleton() { } //此方法是获得 本类 实例 的 全局 唯一的访问点 public static Singleton GetInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
但 上面代码 在 多线程程序中, 多个线程同时访问getinstance();就可能造成创建 多个 实例。所以此时给进程 加一把锁 lock 来进行处理。
Lock 是确保 当一个线程 位于代码 的临界区时,另一个线程 不能进入临界区。如果其他线程 试图进入 临界区 ,则会 一直 处于 等待状态,知道 对象会被释放。
修改代码:
public class Singleton { private static Singleton instance; // 创建一个 静态只读的 进程 辅助对象 private static readonly object syncroot = new object(); /// <summary> /// 把构造函数 声明 为 私有,这样就 阻止了 外界 对 这个类的new 操作 /// </summary> private Singleton() { } //此方法是获得 本类 实例 的 全局 唯一的访问点 public static Singleton GetInstance() { //在同个 时刻,加入锁的 那部分程序 只有一个线程可以进入 lock (syncroot) { if (instance == null) { instance = new Singleton(); } } return instance; } }
不直接 lock (instance) 是因为 不知道 有没有创建过实例,所以只能 在 getinstance()时 加锁。
但是每次 调用 getinstance()时都要 进行 lock() 不好,所以应该在必要时进行lock();
引入双重锁定
public class Singleton { private static Singleton instance; // 创建一个 静态只读的 进程 辅助对象 private static readonly object syncroot = new object(); /// <summary> /// 把构造函数 声明 为 私有,这样就 阻止了 外界 对 这个类的new 操作 /// </summary> private Singleton() { } //此方法是获得 本类 实例 的 全局 唯一的访问点 public static Singleton GetInstance() { //先判断 实例是不是存在, 不存在 再 加锁 锁定 一个 线程 if (instance == null) { //在同个 时刻,加入锁的 那部分程序 只有一个线程可以进入 lock (syncroot) { //此时的 判断实例是不是为空 是 当 有两个 进程 AB 依次执行时, A执行完后 接着 执行B // 因为没有 实例的释放, 要进行 再次 判空 看是否已经 有实例产生了, // 这样 防止了产生多个实例 if (instance == null) { instance = new Singleton(); } } } return instance; } }
在实际的开发中 C# 与 公共 语言运行库 提供了 一种 静态 初始化 的 方法,这种方法不需要开发人员 显示的 编写线程安全代码 ,即可 解决多线程环境下不安全的
问题
public sealed class Singleton { //在第一次引用类的任何成员创建实例 公共语言运行库 负责处理变量初始化 private static readonly Singleton instance = new Singleton(); public static Singleton getinstance() { return instance; } }
由于这种方法 是在 自己 被加载时 就将自己 实例化, 所以形象的称为“ 饿汉式” 单例类 由于一加载就实例化 提前占用系统资源
前面 处理方式 是在 自己 第一次 被 引用时 才 实例化,所以称为“懒汉式 ” 单例类。 面临的更多是多线程 安全 问题 ,需要双向 锁定 才能解决
全局变量 和 单例 模式 的 区别:
全局变量 即对一个对象的静态引用,全局变量确实可以提供单例模式实现的全局访问这个功能,但是,它并不能保证您的应用程序中只有一个实例,同时,在编码规范中,也明确指出,应该要少用全局变量,因为过多的使用全局变量,会造成代码难读,还有就是全局变量并不能实现继承(虽然单例模式在继承上也不能很好的处理,但是还是可以实现继承的)而单例模式的话,其在类中保存了它的唯一实例,这个类,它可以保证只能创建一个实例,同时,它还提供了一个访问该唯一实例的全局访问点。