单例模式1.0:

  1. public class Singleton {
  2. private static Singleton sInstance;
  3. public static Singleton getInstance() {
  4. if (sInstance == null) {  // 1
  5. sInstance = new Singleton();
  6. }
  7. return sInstance;
  8. }
  9. private Singleton() {
  10. }
  11. }

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

单例模式2.0:

  1. public class Singleton {
  2. private static volatile Singleton sInstance;
  3. public static synchronized Singleton getInstance() {
  4. if (sInstance == null) {
  5. sInstance = new Singleton();
  6. }
  7. return sInstance;
  8. }
  9. private Singleton() {
  10. }
  11. }

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

单例模式3.0:

  1. public class Singleton {
  2. private static Singleton sInstance;
  3. public static Singleton getInstance() {
  4. if (sInstance == null) {  // 位置1
  5. synchronized (Singleton.class) {
  6. if (sInstance == null) {
  7. sInstance = new Singleton();  // 位置2
  8. }
  9. }
  10. }
  11. return sInstance;
  12. }
  13. private Singleton() {}
  14. }

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

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

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

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

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

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

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

但是多线程并发时可能会出现以下情况

Java 传统单例模式双重检查锁演化过程 - 图1

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

解决方案1:

  1. public class Singleton {
  2. private static volatile Singleton sInstance;
  3. public static Singleton getInstance() {
  4. if (sInstance == null) {  // 位置1
  5. synchronized (Singleton.class) {
  6. if (sInstance == null) {
  7. sInstance = new Singleton();  // 位置2
  8. }
  9. }
  10. }
  11. return sInstance;
  12. }
  13. private Singleton() {}
  14. }

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

解决方案2:

  1. public class Singleton {
  2. private Singleton(){};
  3. private static class Inner{
  4. private static Singleton SINGLETION=new Singleton();
  5. }
  6. public static Singleton getInstance(){
  7. return Inner.SINGLETION;
  8. }
  9. }

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

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

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