美文网首页Android开发
设计模式之单例模式

设计模式之单例模式

作者: 不会游泳的金鱼_ | 来源:发表于2019-05-24 14:35 被阅读1次

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

    • 定义
    • 使用场景
    • 实现方式
      • 懒汉式
      • 双重检验锁
      • 饿汉式
      • 静态内部类
      • 枚举
    • Android源码中的单例模式
    • 总结

    定义

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

    使用场景

    确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多资源,或者某种类型的对象只应该有且只有一个。(例如创建一个对象需要消耗过多的资源,如访问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获取对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户使用成本,也对用户隐藏了具体实现,降低了耦合度。

    总结

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

    相关文章

      网友评论

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

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