Android学习之设计模式---单例模式

做好计划,定期复盘,感知责任,提高执行力

1 单例模式初识:

1.1 单例模式:

单例模式是指确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例;那为什么要有单例模式呢?比如我们在代码中有一个网络请求的实现类,那在业务需求的场景中,会有频繁的网络请求,那此时如果有一个网络请求,就去new一个网络请求的实现类,这样可能会造成资源的浪费,所以引入了单例模式;

1.2 单例模式的特征或者说创建一个单例模式类的要求:

  • 构造方法不对外开放的(private修饰);
  • 建立一个类静态变量,持有一个自己的实例;
  • 通过一个静态方法或者枚举返回单列类的对象;
  • 注意多线程的场景也要保证单例;
  • 如果单例可以序列化,那要注意单例对象在反序列化时不会重新创建对象;

1.3 单例模式的两种类型:

1.3.1 饿汉式单例模式

public class singletonMode {
      private singletonMode (){} //构造方法private修饰,不对外开放
      private static mSingleton = new singletonMode();//静态变量实例化
      public static singletonMode getInstance(){//通过一个静态方法返回单列类的对象
          retrun mSingleton;
      
}

上面为饿汉式单例模式的实现方式,从代码中可以看出,在类加载的时候mSingleton就已经被实例化了,无论用不用都会被加载,所以可能会造成资源浪费或者加载缓慢(比如说构造方法中做的操作比较多);但是它是线程安全的;
1.3.2 懒汉式单例模式
针对饿汉式单例模式的缺点,则引出了如下的懒汉式单例模式:

public class singletonMode2 {
      private singletonMode2 (){} //构造方法private修饰,不对外开放
      private static mSingleton2 = null();//静态变量
      public static singletonMode2 getInstance(){//通过一个静态方法返回单列类的对象
          if(mSingleton2  == null)//当实例对象为null的时候才去new
              mSingleton2 = new singletonMode2();
          retrun mSingleton2;      
}

以上可以解决饿汉式单例模式在类加载的时候就实例化静态变量,引起资源浪费的问题,此处是在用的时候通过getInstance去拿时,才去new;但是通过仔细观察,懒汉式单例模式存在线程不安全问题,所以需要进一步优化;
优化方式一:对getInstance静态方法添加synchronized关键字

public class singletonMode3 {
      private singletonMode3 (){} //构造方法private修饰,不对外开放
      private static mSingleton3 = null();//静态变量
      public static synchronized  singletonMode3 getInstance(){//通过一个静态方法返回单列类的对象
          if(mSingleton3  == null)//当实例对象为null的时候才去new
              mSingleton3 = new singletonMode3();
          retrun mSingleton3;      
}

相比之下,线程安全提高,但是synchronized是加在静态方法上的,同步锁的颗粒度有点大,所以进一步优化;
优化方式二:这就引出了 双重校验DCL

public class singletonMode4 {
      private singletonMode4 (){} //构造方法private修饰,不对外开放
      private static mSingleton4 = null();//静态变量
      public static singletonMode4 getInstance(){//通过一个静态方法返回单列类的对象
          if(mSingleton4  == null){//第一层校验
              synchronized(singletonMode4 .class){
                  if(mSingleton4 == null){//第二层校验
                      mSingleton4 = new singletonMode4();
                  }
              }
          }
          retrun mSingleton4;      
}

采用方式二则降低了同步锁的颗粒度,同时也考虑了线程安全问题;
注意:但是在JAVA虚拟机中,大家都知道mSingleton4 = new singletonMode4()一句代码它主要包括三个内容:

  • mSingleton4 实例分配对象
  • 调用singletonMode4的构造方法,初始化成员字段
  • 将singletonMode4对象赋值给mSingleton4
    由于在JDK中会进行指令重排,所以有可能会导致DCL失效问题,所以在JDK1.5后引入了volatile禁止指令重排,进而保证了DCL双重检测的有效性;

2 饿汉式与懒汉式单例模式的应用:

饿汉式
在类加载的时候就会实例化对象,无论是否会用到这个对象,都会加载。如果在构造方法里写了性能消耗较大,占时较久的代码,那么就会在启动的时候感觉稍微有些卡顿。
懒汉式
是延迟加载的方式,只有使用的时候才会加载。 并且有线程安全的考量。使用懒汉式,在启动的时候,会感觉到比饿汉式略快,因为并没有做对象的实例化。 但是在第一次调用的时候,会进行实例化操作,感觉上就略慢。

在实际应用中看业务需求,如果业务上允许有比较充分的启动和初始化时间,就使用饿汉式,否则就使用懒汉式

3 单例模式扩展

3.1 单例模式的其他实现方式:

方式一:静态内部类单例模式:

public class singletonMode5 {
      private singletonMode5 (){} //构造方法private修饰,不对外开放
      private static class singletonModeInner {
          private static mSingleton5 = new singletonMode5();
      }
      public static singletonMode5 getInstance(){//通过一个静态方法返回单列类的对象
          retrun singletonMode5.singletonModeInner.mSingleton5 ;   
      }   
}

方式二:使用枚举:

public class Single {
    private Single(){} 
    public enum SingleEnum { 
          singleHandler; 
          private Single single; 
          private SingleEnum () { 
              single = new Single(); 
          }
          public Single getSingle() {
               return single;
           }
     }
    public static Single getInstacne() { 
          return SingleEnum.singleHandler.getSingle(); 
    } 
}

对于以上两种实现方式,枚举是线程安全的。另外还有一些比如说通过容器的方式来实现单例,有兴趣的可以进一步了解一下;

3.2 可序列化的单例类

如果我们的类是可序列化的,那么在反序列化时会破坏单例,生成新的单列类;

private Object readResolve(){
        System.out.println("read resolve");
        return instance;//返回之前定义的单例类对象
    }

这中情况,可以通过重写readResolve()方法,此方法中返回了单例类的对象。具体readResolve解析可参考这篇文章:https://blog.csdn.net/weixin_45433031/article/details/115364766?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-1&spm=1001.2101.3001.4242
小结:本章节了解了设计模式中的单例模式,单例模式在我们的实际开发中是很常见的,大家可以根据自己业务需求,选择不同的实现方式,那重点关注一下DCL双重检测;

你可能感兴趣的:(Android学习之设计模式---单例模式)