贴子以"现状"提供且没有任何担保也没有授予任何权利。
Singleton模式:
意图:保证类有且仅有一个实例对象,并提供对它的全局访问点。
实现:
1. 为了实现以上的意图,首先要绕过常规的实例化对象的手法,即通过new直接实例化对象。因为new的方式在“特殊”时候存在两个缺点:第一个很明显,直接new的方式具有较高的耦合性,缺乏相应的灵活度;第二个缺点,类实例化的主动权被客户端程序掌握,而不为类自身所能控制。也就是说,要确保一个类有且仅有一个实例对象,这应该是类设计者的责任,而不应该是类使用者的责任。
2. Singleton模式的目的是限制类的创建,这是它的重点。所以首先应该将该类的默认构造函数声明为private,以阻止客户端程序使用new直接实例化。并且,声明默认构造函数为private同时也阻止了其他类继承自该类。虽然Singleton能够被继承,但是个人看法是如果Singleton类能够被继承,就有可能产生无数个派生类,这些派生类再实例化对象会导致Singleton模式意图的改变。基于以上的观点,为了突出Singleton类不能够被继承,需要使用明显的关键字来表明(Java的final,C#的sealed),同时也有助于提高JIT的效率。
3. private static的字段表示该类的引用,以及一个对其存取的public static 的方法。该public static的方法就是取得该对象的全局访问点。
UML图示:
经典的Singleton实现:(Java)
(C#)
另一种是采用缓式初始化:(Java)
但是缓式初始化的形式if(instance == null)出现在多线程的情况中的时候就可能具有潜在的副作用。如果两个线程同时进入控制块,则可能会创建该成员变量的两个实例。所以为了保证多线程安全,Singleton模式进一步演化为:
线程安全的Singleton(Java)
Java还有另一种方式实现线程安全的Singleton:
从上面的代码看,无论是java的synchronized还是C#的lock都对进入if(instance == null)代码块的线程进行了互斥访问控制,所以在同一时刻只有一个线程能够进入if(instance == null)代码块,这样就确保了只有一个实例被创建。但是这种方式有一个缺陷就是无论是java的synchronized还是C#的lock都是非常耗费资源的,每次调用GetInstance()方法或者Instance属性都会进行线程同步,这样增加了额外的开销,损失了性能。所以鉴于此,Singleton又演变为Double-checked locking模式。
Double-checked locking模式采用两次检查instance是否被实例化来实现,该模式的有点是克服了上面每次都线程同步消耗大量资源的问题,提高性能。(Java,注:不要使用java的Double-checked locking模式,因为java的对象模型无法保证Double-checked locking模式的有效性)。
在上面的C#代码中,我们除了使用双重检查之外,还添加了volatile关键字,该关键字的含义是保证只有在实例变量分配完成后才能访问实例变量,因为JIT编译器会对代码进行优化,优化后的代码也无法保证对象的唯一性,所以添加volaltile关键字就是明确的告诉编译器不要对此变量进行优化。此种方式解决了线程并发的问题,同时避免了每个Instance属性的调用都出现独占锁定。它还允许将实例化延迟到第一次访问对象时发生。
C#对Singleton的优化(静态初始化):
没错!C#的优化实现就这么简单,而且非常直观。此代码相当于: