再次理解单例模式

386 阅读3分钟

单例模式定义:

确定某个类只有一个实例,而且自行实例化并向系统提供这个实例。

使用场景:

创建对象需要消耗很多资源(如Android当中各种系统提供的Service:InflatorService, AMS, WMS...),或者全局状态管理同步类等。

单例模式写法:

  • 饿汉模式
  public class Singleton {
      private static Singleton sInstance = new Singleton();

      //私有化构造方法 
      private Singleton(){}

      public static Singleton getInstance() {
          return sInstance;
      }
  }
  • 懒汉模式
  public class Singleton {
      private static SingleTon sInstance;
      //私有化构造方法
      private Singleton(){}
      //保证多线程安全  同时每次进来都要获取锁, 浪费锁资源 
      public synchronized static Singleton getInstance() {
          if (sInstance == null) {
              sInstance = new Singleton();
          }
          return sInstance;
      }
  }

需要注意的是这里有一个误区:很多人认为饿汉模式的sInstance在声明的时候就new出来了,所以是在声明的时候就初始化了。饿汉模式中的sIntance到底是何时初始化的?从类的加载机制可以知道 是我们在调用getInstance 引用到sIntance的时候初始化的。当然如果饿汉模式SingleTon暴露出来了其他静态公共属性或方法,那么sIntance也会伴随这些公共属性或方法的访问而被初始化。所以我们说懒汉模式延迟初始化,实际上懒汉模式也是在调用getInstance()方法的时候进行初始化的,并没有达到懒加载的目的。

  • 双重校验锁(Double Check Lock)
  public class Singleton {
      //添加volatile关键字 防止指令重排序
      private volatile static Singleton sInstance;

      //私有化构造方法
      private Singleton(){}

      //此处不加锁 保证性能
      public static Singleton getInstance() {
          if (sInstance == null) {
              //加锁保证多线程安全
              synchronized(Singleton.class) {
                  if (sInstance == null) {
                      sInstance = new Singleton();//创建对象
                  }
              }
          }
          return sInstance;
      }
  }

为什么使用volatile关键字?

这段代码 sInstance = new Singleton(); 实际上在Java JVM中是下面的伪代码:

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

当线程A执行到以上伪代码的时候,第2行和第3行代码可能发生重排序,因为重排序并不会影响运行结果,而且还可以提升性能,所以JVM中允许重排序。设想一个情景:如果2和3发生了重排序,也就是1->3->2,执行到3还未执行2的时候,有个线程B调用了getInstance方法,发现sInstance不为null,直接返回了sIntance,但此时sIntance并没有初始化完成,线程B就会访问到一个未初始化完成的对象。volatile关键字就是用来解决这个问题的,volatile 关键字修饰的变量不允许指令重排序

  • 静态内部类模式(多线程安全)
  public class Singleton {
      //私有化构造方法
      private Singleton(){}

      public static Singleton getInstance() {
          return SingletonHolder.INSTANCE;
      }

      private class SingletonHolder {
          private static final Singleton INSTANCE = new Singleton();
      }
  }

静态内部类模式是通过初始化锁来保证多线程安全的

总结

通过以上几种创建单例模式的理解,我们推荐使用的是Double Check Mode静态内部类模式。 以上内容均是我个人对单例模式的一些理解,如有错误,请留言告诉我哈。么么么哒。