设计模式 - 单例模式

前言

如题,单例模式:用来创建出一个独一无二的,只有一个实例的对象。

应用场景

在线程池(Thread Pool),缓存(Cache),偏好设置,注册表,日志等这些情况下,都需要单一对象;
Android中,可能最常见的就是自己定义的 Base Application 了吧;其他的还有,第三方分享的统一入口,Event Bus的总线,消息机制中的 Message Queue;
另外还有一些具体的场景,比如在订外卖的时候,选择主食、配菜、酒水、餐具、送货地址...等等一个操作流程中,就存在一个唯一的订单对象;
其实,很多地方都有用,只是平常没有注意。

单例 VS 全局变量

  • 实例的唯一性:
    • 全部变量,可以在任意位置被重新赋值,存在着不可预料的风险
    • 单例,在 getInstance() 中,无对象则创建,有则跳过,以此来保证唯一性
  • 创建位置不确定:
    • 全局变量,需要开发者团队协商,按照类别定义在不同的文件中
    • 单例,getInstance() 定义在该类的内部,引用位置明确
  • 初始化时机和内存占用:
    • 全局变量,声明时即初始化,内存全部分配
    • 单例,可以延迟初始化,此时内存占用的很少,

PS:延迟初始化也是Android启动速度优化的一个点

创建的几种方式

按照单例的特性,我们可以总结一下创建的步骤:
1)需要一个类,且只能在该类内部初始化,这样就保证了单例对象不会再其他地方被重新赋值,转化成代码就是,将构造器私有化,像这样 private Singleton() {...}
2)私有化的构造器,无法像 new Singleton() 这样直接调用,于是乎,就有了 public static Singleton getInstance() {...} 这段代码;
3)到这里为止,创建就完成了,接下来我们需要保证唯一性,所以就需要一个静态对象来保持引用 private static Singleton uniqueInstance
4)最简单的单例就完成了,基本代码如下:

public class Singleton {

    private static Singleton uniqueInstance = new Singleton();
    private Singleton() {
    }

    public static Singleton getInstance() {
        return uniqueInstance;
    }    
}

PS:当然以上代码真的是不忍直视(其实也并不正确),接下来我们一点一点优化它。

1,添加懒加载的懒汉式
public class Singleton {

    private static Singleton uniqueInstance;
    private Singleton() {
    }
    
    public static Singleton getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }

        return uniqueInstance;
    } 
}

PS:这段代码简单明了,而且使用了懒加载模式,但是却存在致命的问题。当有多个线程并行调用 getInstance() 的时候,就会创建多个实例。也就是说在多线程下不能正常工作。

2,线程安全的懒汉式:添加 synchronized 关键字修饰 getInstance()
public static synchronized Singleton getInstance() {
    if (instance == null) {
        instance = new Singleton();
    }
    return instance;
}

PS:虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为在任何时候只能有一个线程调用 getInstance() 方法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。这就引出了双重检验锁。

3,双重检验锁

会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。

public static Singleton getSingleton() {
    if (instance == null) {                         // Single Checked
        synchronized (Singleton.class) {
            if (instance == null) {                 // Double Checked
                instance = new Singleton();
            }
        }
    }
    return instance ;
}

PS: 将 instance 变量声明成 volatile,是可以更好的解决线程安全的问题,关于这点,暂且不表。

4,线程安全的单例最简单实现:饿汉式 static final field
public class Singleton{
    // 类加载时就初始化
    private static final Singleton instance = new Singleton();
    
    private Singleton(){}
    public static Singleton getInstance(){
        return instance;
    }
}

PS:这种写法,简单而粗暴,但是并不完美,原因有二。1)不是懒加载;2)无法设置参数。

5,静态内部类 static nested class
public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE; 
    }  
}

PS:这种写法仍然使用 JVM 本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。

6,最简洁的单例:枚举
public enum EasySingleton{
    INSTANCE;
}

PS:没有比这更简单的了~~~

总结

一般来说,单例模式有五种写法:懒汉式、饿汉、双重检验锁、静态内部类、枚举。个人会倾向于使用静态内部类的方式,相对会万金油。

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