今天在看Spring的资料时,提到Spring实现的两种设计模式――单态(单例)模式和工厂模式
由于之前没有接触过单例模式,便找了《java设计模式》学习下。
这一看不打紧,觉得这单例模式很有意思。
单例模式有以下的特点:
单例类只能有一个实例;
单例类必须自己创建自己的唯一的实例;
单例类必须给所有其他对象提供这一实例。
在上面的对象图中,有一个“单例对象”,而“客户甲”,“客户乙”,“客户丙”是单例对象的三个客户对象。可以看到,所有客户对象共享一个单例对象,而且从单例对象到自身的连接线可以看出,单例对象持有对自己的引用。 Windows回收站就是单例模式的应用。
现在要讲到重点了――双重检查成例
先来看看一段代码:
//Broken multithread version
public class ResourceObject {
private static ResourceObject obj=null;
//unnormal private constructor
private ResourceObject()
{}
//double-check locking method
public static ResourceObject getResourceObject()
{
if(obj == null)//first check position
{
//more than one thread will get here
synchronized(ResourceObject.class)
{
//only one thread every single time here
if(obj == null)//second check here
{
obj=new ResourceObject();
}
}
}
return obj;
}
//other functions and members
}
两次检查看似可以省去不必要的同步开销,但是对于java编译器来说这种技巧并不成立。原因在于,java编译器会优化生成的中间代码,导致 ResourceObject类的初始化与变量obj的赋值之间的顺序变得不可预料。在单线程的情况下,这不会导致程序错误,而且也没有同步问题。但是在 多线程的情况下,在前一个线程执行完赋值语句,然后进行剩下的初始化时,后一个线程就会获得未初始化完的对象实例,此时调用ResourceObject 类的方法,就有可能使用初始化过程中未初始化完的类成员,造成程序崩溃。鉴于这一切行为都是无法预测的,双重检查是不建议在java中使用的。
事实上这种双重检查成例与单例模式并无直接的关系,由于很多C语言设计师在单例模式里面使用了双重检查成例,所以这次做法也被很多Java设计师效仿。
在这里还是要提醒一句――双重检查成例在java语言里行不通!!!