Singleton模式:
意图:保证类有且仅有一个实例对象,并提供对它的全局访问点。
实现:
1. 为了实现以上的意图,首先要绕过常规的实例化对象的手法,即通过new直接实例化对象。因为new的方式在“特殊”时候存在两个缺点:第一个很明显,直接new的方式具有较高的耦合性,缺乏相应的灵活度;第二个缺点,类实例化的主动权被客户端程序掌握,而不为类自身所能控制。也就是说,要确保一个类有且仅有一个实例对象,这应该是类设计者的责任,而不应该是类使用者的责任。
2. 模式的目的是限制类的创建,这是它的重点。所以首先应该将该类的默认构造函数声明为private,以阻止客户端程序使用new直接实例化。并且,声明默认构造函数为private同时也阻止了其他类继承自该类。虽然Singleton能够被继承,但是个人看法是如果Singleton类能够被继承,就有可能产生无数个派生类,这些派生类再实例化对象会导致Singleton模式意图的改变。基于以上的观点,为了突出Singleton类不能够被继承,需要使用明显的关键字来表明(Java的final,C#的sealed),同时也有助于提高JIT的效率。Singleton
3. 的字段表示该类的引用,以及一个对其存取的public static 的方法。该public static的方法就是取得该对象的全局访问点。private static
UML图示:
经典的Singleton实现:(Java)
public final class Singleton...{
private static Singleton instance = new Singleton();
public static Singleton GetInstance()...{
return instance;
}
private Singleton()...{}
}
(C#)
public sealed class Singleton...{
private static Singleton instance = new Singleton();
public static Singleton Instance...{
get
...{
return instance;
}
}
private Singleton()...{}
}
另一种是采用缓式初始化:(Java)
public final class Singleton ...{
private static Singleton instance = null;
public static Singleton GetInstance() ...{
// 缓式初始化
if (instance == null) ...{
instance = new Singleton();
}
return instance;
}
private Singleton() ...{
}
}
(C#)
public sealed class Singleton...{
private static Singleton instance = null;
public static Singleton Instance...{
get
...{
// 缓式初始化
if(instance == null)...{
instance = new Singleton();
}
return instance;
}
}
private Singleton()...{}
}
但是缓式初始化的形式if(instance == null)出现在多线程的情况中的时候就可能具有潜在的副作用。如果两个线程同时进入控制块,则可能会创建该成员变量的两个实例。所以为了保证多线程安全,Singleton模式进一步演化为:
线程安全的Singleton(Java)
public final class Singleton ...{
private static Singleton instance = null;
public static synchronized Singleton GetInstance() ...{
// 缓式初始化
if (instance == null) ...{
instance = new Singleton();
}
return instance;
}
private Singleton() ...{
}
}
Java还有另一种方式实现线程安全的Singleton:
public final class Singleton ...{
public final static Singleton instance = new Singleton();
private Singleton() ...{
}
}
(C#)
public sealed class Singleton...{
private static Singleton instance = null;
static readonly object helper = new object();
public static Singleton Instance...{
get
...{
lock(helper)...{
// 缓式初始化
if(instance == null)...{
instance = new Singleton();
}
return instance;
}
}
}
private 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模式的有效性)。
public final class Singleton ...{
private static Singleton instance = null;
public static Singleton GetInstance() ...{
//第一次check
if (instance == null) ...{
synchronized(Class.forName(“Singleton”))...{
//第二次check
if (instance == null) ...{
instance = new Singleton();
}
}
}
return instance;
}
private Singleton() ...{
}
}
而要彻底解决这个问题,还是得等 JSR 133提供的volatile 关键字,用于修饰变量的可变性,强制屏蔽编译器和 JIT 的优化工作。
(C#)
public sealed class Singleton...{
private static volatile Singleton instance = null;
static readonly object helper = new object();
public static Singleton Instance...{
get
...{
// 第一次check
if(instance==null)...{
lock(helper)...{
// 第二次check
if(instance == null)...{
instance = new Singleton();
}
}
}
return instance;
}
private Singleton()...{}
}
在上面的C#代码中,我们除了使用双重检查之外,还添加了volatile关键字,该关键字的含义是保证只有在实例变量分配完成后才能访问实例变量,因为JIT编译器会对代码进行优化,优化后的代码也无法保证对象的唯一性,所以添加volaltile关键字就是明确的告诉编译器不要对此变量进行优化。此种方式解决了线程并发的问题,同时避免了每个Instance属性的调用都出现独占锁定。它还允许将实例化延迟到第一次访问对象时发生。
C#对Singleton的优化(静态初始化):
public sealed class Singleton...{
// 静态初始化
public static readonly Singleton Instance = new Singleton();
private Singleton()...{}
}
没错!C#的优化实现就这么简单,而且非常直观。此代码相当于:
public sealed class Singleton...{
public static readonly Singleton Instance ;
static Singleton()...{
Instance = new Singleton();
}
private Singleton()...{}
}
变量标识为readonly表示只能在静态初始化期间或者类构造函数中分配变量。它是完全依赖于CLR来保证的,即静态构造函数在一个AppDomain中只会调用一次。首先静态构造函数的执行时间只在静态字段初始化之间进行初始化,在静态初始化的时候会将初始化工作放到相应的静态构造函数中进行,而且静态构造函数也可以保证在多线程环境下只有一个线程执行该构造函数。这种方式应该是在.NET中实现Singleton的首选方式。但是这种方式同样存在缺点,就是无法对其进行延迟初始化.
C#对Singleton的优化(延迟初始化):
public sealed class Singleton...{
private Singleton()...{}
public static Singleton Instance...{
get...{
return Nested.Instance;
}
}
class Nested...{
// 显式的静态构造函数告诉C#编译器不要将
// 类型作为beforefieldinit
static Nested()...{
}
internal static readonly Singleton instance = new Singleton();
}
}
因为在嵌套的Nested类中实现了一个静态构造函数(代码中有注释的地方),它会导致编译出来的IL代码中没有beforefieldinit标记,从而使运行库必须在第一次访问该类型的静态或实例字段和方法之前才能做静态构造动作,也就是说此时才会执行internal static readonly Singleton instance = new Singleton();,从而做到了延迟初始化。
在Java中实现可序列化的Singleton模式:
public class MySingleton implements Serializable ...{
static MySingleton singleton = new MySingleton();
private MySingleton() ...{
}
//该方法在该类的对象反序列化的时候马上调用
//该方法返回singleton实例
protected Object readResolve() ...{
return singleton;
}
}
使用Singleton时需要注意的是:
1. Singleton模式一般不要支持ICloneable接口,因为这可能会导致多个对象实例,与Singleton模式的初衷违背。
2. Singleton模式一般不要支持序列化,因为这也有可能导致多个对象实例,同样与Singleton模式的初衷违背。
3. Singletom模式只考虑到了对象创建的管理,没有考虑对象销毁的管理。就支持垃圾回收的平台和对象的开销来讲,我们一般没有必要对其销毁进行特殊的管理。