Android开发

设计模式之单例模式

2019-05-24  本文已影响1人  不会游泳的金鱼_

单例模式是应用最广的设计模式之一,面试的时候常常会被要求写个单例模式的Demo,那么我今天就来看看单例模式的相关内容。
本文的主要内容如下:

定义

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

使用场景

确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多资源,或者某种类型的对象只应该有且只有一个。(例如创建一个对象需要消耗过多的资源,如访问IO和数据库等资源时)

实现方式

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

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

单例模式的实现方式有多种,具体如下:

1. 懒汉式:

感觉命名都是比较形象的称谓。懒人嘛,总是推脱不开的时候才会真正去执行工作,那么在创建对象实例的时候就不着急创建实例,会一直等到马上要使用对象实例的时候才会创建。

public class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    public static synchronized Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

从代码中不难看出getInstance方法添加了synchronized关键字,因此在每次访问getInstance方法式都会进行同步,那么就保证了线程安全。但是,有个明显的缺陷,即使instance已经被初始化,getInstance方法还是会进行同步,一样就会消耗不必要的资源。

2. 双重检验锁(Double Check Lock)

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;
    }
}

DCL在同步锁的基础上,添加1层 if判断:若单例已创建,则不需再执行加锁操作就可获取实例,从而提高性能。

3. 饿汉式

既然饿,那么在创建对象实例的时候就比较着急,饿了嘛,于是在装载类的时候就创建对象实例。

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

饿汉式就比较简洁了,因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。

缺点也很明显,单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。因此饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。

4. 静态内部类

DCL尽管在一定程度上解决了资源消耗过多、多余的同步等问题,但是在某些情况下仍然有失效的可能。因此出现了静态内部类。

public class Singleton {
    private static class SingletonHolder{
        private final static Singleton instance = new Singleton();
    }
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

这种写法仍然使用JVM本身机制保证了线程安全问题。由于静态单例对象没有作为Singleton的成员变量直接实例化,因此类加载时不会实例化Singleton,第一次调用getInstance()时将加载内部类SingletonHolder,在该内部类中定义了一个static类型的变量instance ,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()方法没有任何线程锁定,因此其性能不会造成任何影响。同时由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。

5. 枚举

用枚举写单例实在太简单了,总共就三行代码!这也是它最大的优点。

public enum Singleton {
    INSTANCE;
}

我们可以通过Singleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。

Android源码中的单例模式

使用容器实现单例模式

在Android系统中,存在很多系统级别的服务,如WindowsManagerService、ActivityManagerService等,在程序的初始会以单例的形式统一的注册在系统的管理类中,在使用时根据key获取对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户使用成本,也对用户隐藏了具体实现,降低了耦合度。

总结

单例模式在一般情况下直接使用饿汉式就好了,如果明确要求要懒加载则倾向于使用静态内部类。如果涉及到反序列化创建对象时会试着使用枚举的方式来实现单例。

上一篇下一篇

猜你喜欢

热点阅读