参考书——《HeadFirst设计模式》
首先,来个苏格拉底式的分析,如下:
一个公共类,如果它的构造器是private的,会怎样?
public class Singleton {
private Singleton(){}
}
含有私有构造器的类不能被实例化。
那有可以使用私有构造器的对象么?
该类内的代码估计是唯一能使用这个私有构造器的代码。
为什么?
因为必须有Singleton的实例才能够调用它的构造器,但是又没有其他类能够实例化Singleton。有点像“鸡生蛋,蛋生鸡”的问题。
但是如果加一个这样的方法呢?public static Singleton getInstance()
这个方法不需要用对象调用,可以通过类名.方法名调用。
再进一步,如果这样写呢?是否就可以初始化一个Singleton呢?
public class Singleton {
private Singleton(){}
public static Singleton getInstance(){
return new Singleton();
}
}
当然可以
剖析经典单例模式
public class Singleton {
private static Singleton uniqueInstance; //利用一个静态变量来记录Singleton类的唯一实例
private Singleton() {}
public static Singleton getInstance() {
if(uniqueInstance==null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
====================================================================================================================================
多线程与单例(写到这里,不得不说《HeadFirst设计模式》真的是本很好的书!!!建议以java为语言的人,学习设计模式的时候根本就没有必要看其他的设计模式书,这本讲的最好,当然个人意见。)
上面的经典单例模式就是俗称的“懒汉式”,只有在需要的时候才创建这个实例,这就是“延迟实例化(Lazy instantiaze)”。
遇到多线程怎么办?首先会想到加关键字synchronized到getInstance() 方法,不错!但是,你必须知道同步一个方法可能造成程序执行效率大幅度的下降。那,有没有其他的办法?
“饿汉式”的提出
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
}
利用这个做法,我们依赖JVM在加载这个类时马上创建此唯一的单例实例。但是,对于资源密集,配置开销较大的单例这样的方式就不是很好。
接下来,采用“”双重检查加锁“的单例模式(只需要在需要创建一个实例的时候才进行同步,所以实际上只有在第一次调用getInstance的时候才会出现同步)
代码:
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;
}
}
为什么要进行两次判断?
因为实例为null时多个线程可以并发的进入if内部,然后一个线程进入synchronized块来创建实例,其他线程被阻断。当第一个线程退出synchronized块后,另一个线程就会进入,如果不加第二次判断就会创建多个实例。
关键字 volatile
属性instance是被volatile修饰的。volatile具有synchronized的可见性特点,换而言之,线程能够自动发现volatile变量的最新值。这样,如果instance实例化成功,其他线程便能立即发现。
最后再说一下 类加载器与单例
每个类加载器都定义了一个命名空间,如果有两个以上的类加载器,不同的类加载器可能会加载同一个类,从整个程序来看,同一个类会被加载多次,如果这样的事情发生在单例上,就会产生多个单例并存的怪异现象。解决办法:自行制定类加载器,并制定同一类加载器。