传统单例模式双重检查锁存在的问题

单例模式1.0:java

public class Singleton {
	private static Singleton sInstance;

	public static Singleton getInstance() {

		if (sInstance == null) {  // 1
			sInstance = new Singleton();
		}
		return sInstance;
	}

	private Singleton() {
	}
}

  这种方式很辣鸡,由于多线程环境下不能保证单例。多线程

单例模式2.0:并发

public class Singleton {
	private static volatile Singleton sInstance;

	public static synchronized Singleton getInstance() {

		if (sInstance == null) {
			sInstance = new Singleton();
		}
		
		return sInstance;
	}

	private Singleton() {
	}
}

  这种方式也很辣鸡,由于多线程环境下每一个线程执行getInstance()都要阻塞,效率很低。优化

单例模式3.0:spa

public class Singleton {
	private static Singleton sInstance;
	public static Singleton getInstance() {
		if (sInstance == null) {  // 位置1
			synchronized (Singleton.class) {
				if (sInstance == null) {
					sInstance = new Singleton();  // 位置2
				}
			}
		}
		return sInstance;
	}
	private Singleton() {}
}

  这种方式使用双重检查锁,多线程环境下执行getInstance()时先判断单例对象是否已经初始化,若是已经初始化,就直接返回单例对象,若是未初始化,就在同步代码块中先进行初始化,而后返回,效率很高。线程

  可是这种方式是一个错误的优化,问题的根源出在位置2对象

  sInstance =new Singleton();这句话建立了一个对象,他能够分解成为以下3行代码:blog

memory = allocate();  // 1.分配对象的内存空间
ctorInstance(memory);  // 2.初始化对象
sInstance = memory;  // 3.设置sInstance指向刚分配的内存地址

  上述伪代码中的2和3之间可能会发生重排序,重排序后的执行顺序以下排序

memory = allocate();  // 1.分配对象的内存空间
sInstance = memory;  // 2.设置sInstance指向刚分配的内存地址,此时对象尚未被初始化
ctorInstance(memory);  // 3.初始化对象

  由于这种重排序并不影响Java规范中的规范:intra-thread sematics容许那些在单线程内不会改变单线程程序执行结果的重排序。内存

  可是多线程并发时可能会出现如下状况

  线程B访问到的是一个还未初始化的对象。

解决方案1:

public class Singleton {
private static volatile Singleton sInstance;
public static Singleton getInstance() {
        if (sInstance == null) {
                synchronized (Singleton.class) {
                        if (sInstance == null) {
                           sInstance = new Singleton();
                            }
                        }
                    }
                    return sInstance;
            }
private Singleton() {}
}

  将对象声明为volatitle后,前面的重排序在多线程环境中将会被禁止

解决方案2:

public class Singleton {
	private Singleton(){};
	private static class Inner{
		private static Singleton SINGLETION=new Singleton();
	}
	public static Singleton getInstance(){
		return Inner.SINGLETION;
	}
}   

 

  静态内部类不会随着外部类的初始化而初始化,他是要单独去加载和初始化的,当第一次执行getInstance方法时,Inner类会被初始化。

  静态对象SINGLETION的初始化在Inner类初始化阶段进行,类初始化阶段即虚拟机执行类构造器<clinit>()方法的过程。

  虚拟机会保证一个类的<clinit>()方法在多线程环境下被正确的加锁和同步,若是多个线程同时初始化一个类,只会有一个线程执行这个类的<clinit>()方法,其它线程都会阻塞等待。

相关文章
相关标签/搜索