单例模式的常见形式如下:
形式一:
public class Singleton{
private Singleton(){}
private static Singleton instance = new Singleton{};
public static Singleton getInstance(){
return instance;
}
}
这里之所以把构造函数设置成私有的,就是为了防止其它对象私自创建Singleton对象,保证单例的一个策略.
形式二:
public class Singleton{
private Singleton(){}
private static Singleton instance = null;
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
这个模式个人认为已经是23个经典模式中最为简单,且最被广为人知的几个模式之一了。
但是程序员的世界中,追求极端优化是那些优秀程序员最喜欢干的事,上面的两个代码都是有缺点的:
形式一的缺点是:无论这个模式是不是被使用,都会被创建
形式二的缺点是:所有调用getInstance()的方法都会被进入单线程模式,执行变慢.
为了消除这些缺点,有些程序员创建了doubule checked locking idiom,见下:
由于形式二中只有创建的时候是需要单线程的,所以有下面的这个变种,
public class Singleton{
private Singleton(){}
private static Singleton instance =niull;
public static Singleton getInstance(){
if(instance == null){ //1
synchronized(Singleton.class){ //2
if(instance == null){ //3
instance == new Singleton(); //4
}
}
}
return instance;
}
}
表面看起来这段代码相当的精妙,可是由于jvm的优化,Out-of-order writer引起了一些问题,具体见文章:
dobule checked locking:http://www.ibm.com/developerworks/java/library/j-dcl.html,
这里我只是稍微提点下,因为本人的英文不好,所以看上面文章很吃力,目前也只是半懂,Out-of-order指的是由于有些JVM为了优化性能,在不影响结果的前提下,通常会将代码的执行顺序变动一下,比如创建对象正常的流程应该是:
1.分配内存空间
2.调用构造器
3.将值赋值给instance。
优化后的jvm执行顺序可能会变成
1.分配内存空间
2.将内存空间赋值给instance
3.调用构造器.
可能产生的情况如下:
1.线程1进入//1
2.线程1判断instance为null,进入//2的锁
3.线程2进入//1
4.线程2判断instance为null,但是此时锁是在线程1那里,线程2等待
5.线程1进入//3
6.线程1判断instance为null,创建对象,释放锁
7.线程2得到锁,进入到//3
8.由于6中的对象创建只是进行了分配内存空间,将内存空间赋值给instance这两步,所以判断instance非空,返回instance,不过这个instance并不是预期希望获取的值,出错.
也有网友对于Out-of-order wirter的问题写了一些解决方案,不过貌似他没看完上面的文章,不过这篇文章的评论是相当的经典,推荐:
对于单例模式的一点想法:http://lucaslee.javaeye.com/blog/211471?page=2#comment
private static int hasInitialized=0;
private static Singleton INSTANCE;
public static Singleton getInstance(){
if(hasInitialized==0){
synchronized(Singelton.class){
//Double checking
if(hasInitialized==0){
INSTANCE=new Singleton();
hasInitialized=1;
}
}
}
}
文章的主人认为由于hasInitialized这个int类型的操作是原子的,所以这样做就不会出现问题了,如果JVM是按照代码的顺序来执行的,那的确是没有问题了,不过此文的主人没有看完IBM那篇文章下面关于
经过广泛讨论,由于jvm的性能优化,在多线程状态下,lazy initial既要高效率又要安全基本不靠谱,最安全的做法还是只有文章的前两种,不过两种性能开销都较大,但是能换来比较安全的执行。 在现实中,一般会使用形式一,第一这些类既然被创建了,基本都会被用到,而且由于只有一个实例,其实从整体而言是大大节省了内存的开销,也变相提升了执行效率。我想目前的单例模式多用于节省内存,配合工厂模式一起使用吧.