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

单例模式1.0:

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:

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行代码:

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>()方法,其它线程都会阻塞等待。

posted @ 2018-11-30 19:49  君奉天  阅读(6024)  评论(0编辑  收藏  举报