美文网首页
设计模式 - 单例模式

设计模式 - 单例模式

作者: 暗影飞客 | 来源:发表于2017-12-17 21:59 被阅读0次

    前言

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

    应用场景

    在线程池(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:没有比这更简单的了~~~

    总结

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

    相关文章

      网友评论

          本文标题:设计模式 - 单例模式

          本文链接:https://www.haomeiwen.com/subject/xjycvxtx.html