Singleton单例模式
学习笔记
《设计模式》中对单件模式的介绍:
使用单件模式的主要意图就是控制该类只能够创建一个实例,同时向客户程序提供一个访问它的全局访问点。
实际上,单件模式要做的就是通过控制类型实例的创建过程,确保客户程序使用的都是创建好的同一个实例。
C#代码描述:
public class Singleton { static public Singleton instance ; // 唯一实例 protected Singleton () { } //封闭客户程序的直接实例化 public static Singleton Instance //全局访问点 { get { if ( instance == null) instance = new Singleton(); return instance; } } }
这段代码已满足经曲单件模式的设计要求,大多数情况下也可以很好地工作,但在多线程环境下,还存大很多缺陷。
在综合执行效率和线程同步的考虑后,采用一个Double Check的方式修正上面的代码如下:
C#增加Double Check后的Singleton
public class Singleton { protected Singleton (){} static volatile Singleton instance = null ; //Lazy方式创建唯一实例的过程 public static Singleton Instance() { if(instance == null ) //外层 if lock(typeof(Singleton)) //多线程中共享资源同步 if(instance == null ) // 内层if instance = new Singleton(); return instance; } }
Double Check方式有以下几处改进:
- 外层if,避免客户程序每次执行时都要先lock住Singleton类型。每次都锁定Singleton类型会导致效率低下。
- lock加内层if组成了一个相对线程安全的实例构造环境。
- 一旦唯一的实例被创建之后 ,后续发起的调用都无须经过lock部份,直接在外层if判断之后就可获得既有的唯一实例引用。
- volatile关键字,表示字段可能被多个线程修改,声明为volatile的字段不受编辑器优化限制,确保该字段在任何时间都是最新的值,
也就是说在被lock之后,如果还没有真正完成new Singleton(),新加入的线程看到的instance都是null;
引用MSND在线文章(Exploring the Singleton Design Pattern)方式:
sealed class Singleton { Singleton() { } public static readonly Singleton Instance = new Singleton(); }
代码非常简洁精干,功能与Double Check方式相同,是多线程环境下C#实例单件模式的非常棒的方式。
- 它省去了Double Check方式中的laze构造过程。由于Instance是类的公共静态成员,因些它会在类第一次被用到的时候构造出来,这样就不用把它的构造语句显式地写在静态构造函数中。
- 这里实现构造函数被定义为私有的,所以客户程序能其子类从外部构造新的实例,只能通过公共静态成员Instance引用其唯一的实例,符合Singleton的设计意图。
- 通过编译后的IL代码来分析,IL代码中有一个beforefieldinit修饰符,它告诉CLR这里的静态成员(Instance)只有在静态构造函数执行后才生效,
因些即使有很多线程试图引用Instance,也需要等待静态构造函数执行并把静态成员Instance实例化之后才可以使用。
其次,IL里有一个initonly,表示Instance一旦创建,就不能被任何线程修改,也就不用Double Check了。
在实际项目中,有很多因素会打破我们费尽心思实现的Singleton,这些隐蔽很深、一旦发生破坏性又很大的隐患可能会导致全局业务流程出现混乱。下面是两个情景:
- 不要实现ICloneable接口或继承自其相关子类,否则客户程序可以跳过已经隐蔽起来的类构造函数。下面示例就导致非单一实例存在:
public class BaseEntity : System .ICloneable { public object Clone() //对当前实例进行克隆 { return this .MemberwiseClone(); } } public class Singleton : BaseEntity { //... }
- 严防序列化。序列化本身会导致Singleton特性的破坏,因为序列化事实上完成了Singleton对象的复制和重现,所以不能对期望具有Singleton特性的类型声明SerializableAttribute属性。