单例模式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) { // 位置1
synchronized (Singleton.class) {
if (sInstance == null) {
sInstance = new Singleton(); // 位置2
}
}
}
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类初始化阶段进行,类初始化阶段即虚拟机执行类构造器()方法的过程。
虚拟机会保证一个类的()方法在多线程环境下被正确的加锁和同步,如果多个线程同时初始化一个类,只会有一个线程执行这个类的()方法,其它线程都会阻塞等待。