Android 面试之单例模式

455 阅读4分钟

单例模式是java设计模式之一。这种模式涉及到一个单一的类,该类负责创建自己的对象,并确保是单一的对象。这个类提供直接访问其单一对象的方式,且不需要实例化该类的对象。

特点

  1. 单例类只能有一个实例
  2. 单例类必须自己创建自己唯一的实例。构造函数是私有的,外部是无法实例化该类。
  3. 单例类必须给所有其他对象提供这一实例。

优缺点

  1. 优点
  • 减少程序内部实例数目,节省系统资源
  • 全局使用的实例可以避免其频繁的创建与销毁
  • 避免对资源的多重占用
  1. 缺点
  • 没有接口,不能继承
  • 与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化

实现

  1. 按照概念,我们实现一下单例模式,如下:
public class SingleInstanceClass {
    //创建自己的对象
    private static SingleInstanceClass instanceClass=new SingleInstanceClass();
    //私有构造方法
    private  SingleInstanceClass(){

    }
    //对外提供获取该类对象的方法
    public static SingleInstanceClass getInstanceClass(){
        return instanceClass;
    }
}

这就完了吗?

那必须接着折腾,上面代码虽然完全实现了单例。但是会发现,如果程序中没有使用到这个对象,他依然会在编译时创建对应的实例,这样就浪费了资源。于是便有了懒加载的创建单例方式。

  1. 懒加载模式 按照上面的分析,为了不浪费资源我们需要在使用给对象的时候再去创建它的实例对象,也就是懒加载模式。看下面的代码:
public class SingleInstanceClass {
    private static SingleInstanceClass instanceClass;
    //私有构造方法
    private  SingleInstanceClass(){

    }
    //对外提供获取该类对象的方法,且调用此方法时,创建实例
    public static SingleInstanceClass getInstanceClass(){
        //保证唯一性
        if(instanceClass==null){
            instanceClass=new SingleInstanceClass();
        }
        return instanceClass;
    }
}

与放法一不同之处在于,只有当我们使用SingleInstanceClass.getInstanceClass()方法时才会实例化该对象。但是,如果是在多线程中呢?这种方式又有弊端了,多线程有可能依然会多次实例化这个对象。那为解决这个问题我们来看第三种方式。

  1. 懒加载,线程安全方式
public class SingleInstanceClass {
    private static SingleInstanceClass instanceClass;
    //私有构造方法
    private  SingleInstanceClass(){

    }
    //对外提供获取该类对象的方法,且调用此方法时,创建实例,加入线程锁
    public static synchronized SingleInstanceClass getInstanceClass(){
        //保证唯一性
        if(instanceClass==null){
            instanceClass=new SingleInstanceClass();
        }
        return instanceClass;
    }
}

synchronized 同步锁,多线程并发时,同一时间只会执行一个线程。

这种方法因为加了锁,会导致执行效率变低,于是乎为了提高运行效率,且又能保证线程安全。又演变出第四中方式。

  1. 双检锁/双重校验锁(DCL,即double-checked locking)
public class SingleInstanceClass {
    private volatile static SingleInstanceClass instanceClass;
    //私有构造方法
    private  SingleInstanceClass(){

    }
    //对外提供获取该类对象的方法,且调用此方法时,创建实例
    public static  SingleInstanceClass getInstanceClass(){
        //保证唯一性
        if(instanceClass==null){
          synchronized (SingleInstanceClass.class){
              if(instanceClass==null){
                  instanceClass=new SingleInstanceClass();
              }
          }
        }
        return instanceClass;
    }
}

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。2. 禁止进行指令重排序

除此之外还有其它的方式如下

  1. 登记式/静态内部类 这种方法,要比上面的方法实现上面简单许多,且效果是一样的。
public class SingleInstanceClass {
    //静态内部类中创建外部类的实例
    private static class SingleHolder {
        private static SingleInstanceClass INSTANCE = new SingleInstanceClass();
    }

    //私有构造方法
    private SingleInstanceClass() {

    }

    public static SingleInstanceClass getInstance() {
        return SingleHolder.INSTANCE;
    }

}

经验之谈:一般情况下,不建议使用第 1 种和第 2 种方式,建议使用第 3 种方式。只有在要明确实现 懒加载效果时,才会使用第 5 种登记方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。

👇我的公众号欢迎大家关注