单例模式-双重检测
在单例模式的实现方式中,除了确保单例类的构造函数是private,还需要提供一个public static的方法来获取实例
public synchronized Resource getResource(){
if (resource == null){
resource = new Resource();
}
return resource;
}
在并发编程的环境下,这种懒加载的方式由于考虑到线程同步的问题,需要对单例模式下获取实例的方法进行synchronized关键字加锁,这样就会造成每次访问获取实例方法的时候都要进行同步,为了减少同步的开销,我们引入了双重检查模式。
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
这样的设计只会产生一个实例,并且只在初始化的时候加同步锁,但是会引发指令重排序问题
指令重排序是为了优化指令,提高程序运行效率。指令重排序包括编译器重排序和运行时重排序。JVM规范规定,指令重排序可以在不影响程序执行结果的情况下进行。基于指令重排序问题的考虑,进行了一下修订。
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
Singleton temp = instance;
if (temp == null) {
synchronized (Singleton.class) {
temp = new Singleton();
}
instance = temp;
}
}
}
return instance;
}
修订版本视图引进局部变量和第二个synchronized关键字来解决指令重排序的问题,但是Java语言虽然规定了同步代码块内的代码必须在对象锁释放之前执行完毕,却没有规定同步代码块之外的代码不能在对象锁释放之前执行,换言之,instance=temp可能在编辑器或者运行期移动到里层的sychronized内,于是会引发和之前一样的问题。在JDK1.5之后,可以使用volatile变量来禁止指令重排序,从而使双重检查机制生效。
public class Singleton {
private static volatile Singleton instance;private Singleton() { } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; }
}