在Java编程中,Singleton 模式可算得上是应用最广泛的一种设计模式了。它大量得被用于各种流行的框架中,包括最基本的 JDK 代码中。从代码来说, Singleton 的实现很简单,但是真正用好并不容易,几年的开发设计工作中经常都遇到由于没有弄清实例的单态或非单态性,以及不恰当得使用单态导致的问题。
基本概念:
从分类来说,Singleton 属于创建型(Construction)模式,最基本的目标是要保证在一个JVM中,一个 Class 最多只有一个实例存在。而要达成这个目标背后的驱动力是: 减少不必要的资源和时间的开销。
- 资源:在内存中每个对象实例都要占据一定空间,当程序不需要为每个线程在该对象中保存不同的状态时,就可以用Singleton 模式,永远只用一个对象实例为所有线程服务。
- 时间:在JVM中每次构造一个对象实例都是有一定时间消耗的,用Singleton 模式可以帮助程序提高性能,只有第一次初始化实例时需要时间消耗,以后每次引用的时间消耗都几乎为零。
Singleton 最根本的价值就在于“节省资源”!尽管这种设计被广泛使用,但是在单线程或低并发环境中,它在性能和资源节省上带来的价值并不大,越是高并发的多线程环境,Singleton 所能带来的价值越明显!(当然前提是你能够正确使用它)
反驳:
1. 现在有些文章(其实是老外N年前就讨论过的问题)在评论到底还需不需要用 Singleton 模式,在讨论 Singleton 是不是邪恶的:) 其实辩证地看,世间被就没有什么东西是绝对好或不好,关键看你怎么利用它。如果程序员对 Singleton 模式理解不深,不恰当使用,确实会导致严重问题,但这不代表这个模式是不该存在的。就如同C语言的指针,很多人用错,但是不能说指针就是一个错误的设计。是天使还是魔鬼都取决与利用它的人。
2. 现在有些人说 Singleton 已经过时了,不需要了,仿佛 IOC 模式已经把一切关于 bean 创建的问题都解决了。我不这样认为。也许现在程序员或架构师已经不需要自己实现 Singleton 了,但这只是因为 Singleton 的实现已经被一些成熟的框架包办,程序员不需要自己去关心了,并不是说 Singleton 不存在。我们仍然需要控制对象实例的数目来达到节省资源,减少开销的目的!如果程序员对 Singleton 没有足够的理解,也很难正确有效得使用帮我们包办一切的框架,如 Spring。
模式实现:
经过人们对 Singleton 多年的使用,通常有两种公认的线程安全的实现(并非全部):
1. Lazy initialization:
提供一个synchronized getInstance() 方法来检查对象实例是否已创建。如果是,直接返回引用;如果不是,创建实例并返回。并将缺省的构造器方法定义为 private:
class Singleton {
private static Singleton _instance;
private int _state;
private Singleton() {
_state = 0;
}
public static synchronized Singleton getInstance() {
if (_instance == null)
_instance = new Singleton();
return _instance;
}
}
2. Aggressive initialization:
抛弃 synchronized,而使用静态属性,在类载入时立即初始化,同样需要把缺省的构造器方法定义为 private:
class Singleton {
private int _state;
private static Singleton _instance = new Singleton();
private Singleton() {
_state = 0;
}
public static Singleton getInstance() {
return _instance;
}
}
问题分析:
1. 上面的第一种实现虽然可行,但是有一个缺点就是多余的 synchronized 消耗:事实上 Singleton 的实例化只有在第一次实际进行 new Singleton() 的时候需要 synchronized,从那以后每次调用 getInstance() 方法只需要简单返回 _instance 引用就可以了,而此时 synchronized 需要的消耗就成了浪费!
2. 我曾经在不止一次的项目中看到过如下的代码来实现 Singleton,这是为了解决上面的 synchronized 浪费:
class Singleton {
private static Singleton _instance;
private int _state;
private Singleton() {
_state = 0;
}
public static Singleton getInstance() {
if (_instance == null) {
synchronized (Singleton.class) {
_instance = new Singleton();
}
}
return _instance;
}
}
这个实现在单线程环境下不会出问题,但是放到并发的环境中是有问题的,线程并不真正安全。多个线程有可能同时进入 if(_instance == null) 内部,而导致程序实际创建出多个对象实例!
3. 还有一种 Double-checked locking 的实现,试图解决上面两个问题:
class Singleton {
private static Singleton _instance;
private int _state;
private Singleton() {
_state = 0;
}
public static Singleton getInstance() {
if (_instance == null) {
synchronized (Singleton.class) {
if (_instance == null)
_instance = new Singleton();
}
}
return _instance;
}
}
可以看到这个实现煞费苦心,在 synchronized 内部再判断一次 if(_instance == null)。于是在单线程和低并发环境下,这个实现很难出问题了,但是到了足够高并发的环境中,线程再次变得不安全。这个问题是由 Java 平台的内存模式引起的,也与不同的 JIT 编译器的编译方式有关,称之为“out-of-order writes”:一个实例 _instance 有可能在 new Singleton() 没有完全初始化的时候就已经不再是 null,于是并发线程可能得到一个没有完全初始化的实例,从而引起错误。
有很多关于 Double-checked locking 或 out-of-order writes 的分析文章,推荐:
Double-checked locking and the Singleton pattern
4. 看过一些关于 Singleton 的文章,提到 Singleton 的另一个用途,可以用来保持全局状态,如网站计数器。确实 Singleton 可以帮助我们达到这个目的,但是仔细想想,其实任意一个类静态变量都可以达到这个目的,Singleton不是必需。而且考虑 Singleton 模式使用中带有的陷阱,它并不是一个好的办法来达到保持全局状态的目的。
5. Singleton 模式只能保证在单 JVM 中只创建一个对象实例!相同的代码一旦部署到集群或分布式环境中就可能出错,Singleton 完全失效了!在分布式环境中,StatelessSession bean 是一个好的选择。
6. 综上所述,Singleton 模式有它特定的适用场合,达到特定的目的(节省资源!)。除非必要,尽量不要用 Singleton!