单例模式

Android进阶之设计模式

单例模式

定义: 保证一个类仅有一个实例,并提供一个访问它的全局访问点.
使用场景:

  1. 整个项目需要一个共享访问点或者共享数据.
  2. 创建一个对象需要耗费的资源过多,比如访问I/O 或者数据库资源.
  3. 工具类对象;

实现单例模式主要有以下几个关键点:

  1. 构造函数不对外开放,一般为private.
  2. 通过一个静态方法或者枚举返回单例类对象.
  3. 确保单例类的对象有且只有一个,尤其是在多线程环境下.
  4. 确保单例类对象在反序列化时不会重新构建对象.

下面是单例模式的6种写法;

  • 1 饿汉模式
public class Singleton {

    private static Singleton instance = new Singleton();
    private Singleton(){
    }
    
    public static Singleton getInstance(){
        return instance;
    }
}

在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快. 这种基于类加载机制,避免了多线程同步的问题. 在类加载的时候就完成实例化,没有达到懒加载的效果,如果从始至终未使用过这个实例,就会造成内存的浪费;

  • 2 懒汉模式(线程不安全)
//懒汉模式(线程不安全)
public class Singleton {
    private static Singleton instance;
    private Singleton (){
    }
    
    public static Singleton getInstance(){
        if (instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

懒汉模式声明了一个静态对象,在用户第一次调用时初始化.这虽然节约了资源,但第一次加载时需要实例化,反应稍慢一些,且在多线程时不能正常工作;

  • 3 懒汉模式(线程安全)
//懒汉模式(线程安全)
public class Singleton {
    private static Singleton instance;
    private Singleton() {
    }
    
    public static synchronized Singleton getInstance() {
        if (instance==null){
            instance = new Singleton();
        }
        return instance;
    }
}

这种写法能够在多线程中很好的工作,但每次调用getInstance方法时都需要进行同步,这个会造成不必要的同步开销,而且大部分的时候我们用不到同步的,所以,不建议用这种模式;

  • 4 双重检查模式(DCL)
//双重检查模式(DCL)
public class Singleton {
    private volatile static Singleton instance;
    
    private Singleton(){
    }
    
    public static Singleton getInstance(){
        if (instance == null){
            synchronized (Singleton.class){
                if (instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

这种写法在 getInstance 方法中对 instance 进行了两次判空,第一次是为了不必要的同步,第二次是在instance 等于null 的时候才创建实例,在这里使用 volatile 会或多或少影响性能,但是考虑到程序的正确性,牺牲这点性能还是值得的. DCL 的优点就是资源利于率高,第一次执行getInstance时单例对象才被实例化,效率高. 缺点是在第一次加载时反应稍微慢一点,在高并发环境下也有一定的缺陷,DCL虽然在一定程度上解决了资源的消耗和多余的同步,线程安全问题,但在某些情况下还是会出现失效的问题,也就是DCL 失效.所以建议用静态内部类单例模式来替代DCL.

DCL 失效: instance = new Singleton(); 这句代码,它并不是一个原子操作,这句代码最终会被编译成多条汇编指令:

  1. 给 Singleton 的实例分配内存.
  1. 调用 Singleton() 的构造函数,初始化成员字段.
  2. 将 instance 对象指向分配的内存空间(此时 instance 就不是 null 了).

但是,由于 Java 编译器允许处理器乱序执行, 上面的 2,3 执行顺序无法保证. 可以是1 2 3,也可能是1 3 2,如果是1 3 2,多线程的情况下,3 执行完毕,2 未执行,被切换到其他线程上, 由于此时 instance 已经不是空的了,所以,线程 B 直接取走 instance ,再使用的时候就会出错, 这就是 DCL 失效问题

在 JDK 1.5之后,SUN 官方已经注意到这个问题,调整 JVM ,具体化了 volatile 关键字,因此在 JDK 1.5及之后的版本,使用上面的 private volatile static Singleton instance; 就可以保证 instance 对象每次都是从主内存中读取,就可以使用 DCL 的写法来完成单例.
DCL 模式是使用最多的单例实现方式,它能够在需要时才实例化单例对象.并且在绝大多数场景下保证单例对象的唯一性.

  • 5 静态内部类单例模式
//静态内部类单例模式
public class Singleton {
    private Singleton(){

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

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

第一次加载 Singleton 类时并不会初始化 sInstance ,只有第一次调用getInstance方法时虚拟机加载 SingletonHolder 并初始化 sInstance . 这样不仅能确保线程安全,也能保证 Singleton 类的唯一性,同时也延迟了单例的实例化. 所以,推荐使用静态内部类单例模式;

    1. 枚举单例
//枚举单例
public enum Singleton {
    INSTANCE;
    public void doSomeThing(){
        
    }
}

默认枚举单例的创建是线程安全的,而且在任何情况下都是单例.

上面的单例模式的实现中,有一种情况下会重新创建对象,那就是反序列化: 将一个单例对象写到磁盘再堵回来,从而获得一个实例.反序列化操作提供了readResolve 方法,这个方法可以让开发人员控制对象的反序列化.如要避免,需要加入如下方法:

    private Object readResolve() throws ObjextStreamException{
        return instance;
    }

也就是 readResolve 方法中将 instance 对象返回,而不是默认的重新生成一个新的对象,而对于枚举,并不存在这个问题.

其实在我们平时开发中,用到单例模式的情况很多.以上就是单例模式的介绍.

你可能感兴趣的:(单例模式)