public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance(){
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
private static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance(){
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
这是典型的Singleton的实现,在single-threading下,它没有任何问题。但是,你能告诉我在multi-threading下,可 能会发生什么事情么?multi-threading是顽皮的小孩,它不保证各个线程执行的顺序,所以,会有一种可能,if (uniqueInstance == null)这个判断会在不同的线程中连续执行。让我们看看最简单的、只有两个线程的情况:
thread 1 thread 2 uniqueInstance
if(uniqueInstance == null) null
if(uniqueInstance == null) null
uniqueInstance = new Singleton(); new object
uniqueInstance = new Singleton(); new object
看到了么?产生了两个不同的Object!这不一定永远是坏事,比如这是一个在线购物的控制系统,那么你可能会用一件衣服的钱买到两件衣服,虽然也有可能 是你付了钱,但是没得到衣服,但是概率是50%对50%的,也不是什么大不了的事儿~但是,如果这是一个铁路调度控制系统呢?火车也许会相撞。如果这是一 个核反应堆的控制系统呢?一个城市可能就变成废墟了(当然,也许你希望这发生在日本)。
OK!我们达成了一致,这确实是个大问题。我们怎么解决它?“我们可以用synchronized!”有人兴奋的叫到:“真是个好主意!”好,我们来试试看~
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
public static synchronized Singleton getInstance(){
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
好样的!现在同一时刻只有一个线程能调用getInstance()方法,不会存在城市被炸平的悲剧了,我们拯救了几百万人(上海的话,一千多万人)!我 代表他们感谢你,但是,过不了多久就有人会站出来抱怨:“为什么系统的速度变慢了?”这些没良心的人们,这么快就忘记了这段code拯救了他们的性命,竟 然抱怨起了这么小的问题!愤愤之余,你开始思考速度变慢的原因。private static Singleton uniqueInstance;
private Singleton() {}
public static synchronized Singleton getInstance(){
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
啊,synchronized,它使得原本并行的访问变成了串行,这是性能的瓶颈,有什么办法可以解决吗?当然,有人站了出来:“我有投机取巧的方法。”
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
}
问题解决了~JVM会在所有线程之前先实例化一个Object,所有的getInstance()调用都是在引用这个预先产生的Object,不会有任何的问题啦~private static Singleton uniqueInstance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
}
但是,完美主义的我们并不满意:“无论是否使用这个Class,都会预先产生一个Object,这很不优雅!”那我们就看看优雅的解决方案。考虑一下 multi-threading下出现错误的原因,并不是因为不同的线程同时调用了getInstance()方法,而是if (uniqueInstance == null)出现了意想不到的排列,也就是说,只有在产生Object的时候,才会有multi-threading的问题,当 uniqueInstance != null时,多少个线程都不会有问题。因此,我们得到了结论:只需要对创建Object的code上锁就可以了。
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
关键字volatile可以保证uniqueInstance可以被同时运行的若干个线程正确的修改,而getInstance()方法体中的写法则保证 了细力度的线程锁。注意,uniqueInstance == null被判断了两次,这是必要的,这种方法叫做Double-checked locking。private volatile static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
噹噹噹噹~任务完成,Singleton不在惧怕multi-threading啦!让我们来看看关于Java实现Singleton的tricky things。
- Double-checked locking最好只在Java 5中使用。在Java 1.4以及之前版本的诸多实现中,volatile关键字都被错误的实现,这会导致Double-checked locking的不正确执行。
- 在Java 1.2以及之前的版本中,JVM的垃圾回收器存在一个bug:当一个Singleton的Object不存在全局引用(即只有自身的instance变量 在引用这Object)的时候,垃圾回收器会回收这个Object。也就是说,在这种情况下,getIntance()会得到一个新的Object,这不会导致城市被炸平,但是会导致做了10年的核反应成果突然消失,当然不是我们想要的。
- Java允许不同的Class Loader创建自己的Object,它们用namespace来区别这些Object。因此,当你的Singleton设计Class Loader时,最好在获取Object的时候同时指定它的Class Loader。