美文网首页
Android系统编程思想篇:单例模式

Android系统编程思想篇:单例模式

作者: 江湖再见2024 | 来源:发表于2017-03-20 17:03 被阅读30次

Android系统编程思想篇:单例模式

关于作者

郭孝星,程序员,吉他手,主要从事Android平台基础架构方面的工作,欢迎交流技术方面的问题,可以去我的Github提issue或者发邮件至guoxiaoxingse@163.com与我交流。

文章目录:https://github.com/guoxiaoxing/android-open-source-project-analysis/blob/master/README.md

单例模式可能是我们最常见的模式之一了,在单例模式中,我们要求一个系统只有一个全局对象存在,这样有利用我们去协调系统的整体行为。比如在某个服
务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置
信息。这种方式简化了在复杂环境下的配置管理。

模式定义

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

单例模式常见的使用场景

某个类的对象有且只有一个对象存在,这种场景分以下两种情况:

1 创建对象需要消耗大量资源,保证对象单一共享,减少资源消耗。
2 对象本身具有唯一性,不应该多次创建。

单例模式有哪些优缺点呢?

优点:

1 单例模式只有一个实例,减少类系统开销,避免了对资源的多重占用。
2 单例模式可以设置全局访问点,优化和共享资源访问。

缺点:

1 单例一般没有接口,后期做扩展比较困难,只能修改源码。
2 单例对象如果持有Context,容易引发内存泄漏,所以我们一般传递的是Application Context。

模式实现

单例模式实现要点:

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

针对以上的实现要点,单例模式的实现一般说来有5种实现方式:

1 懒汉式单例
2 双层校验锁单例
3 容器单例
4 静态内部类单例
5 枚举单例

6种模式各有利弊,我们分别来看看这6种模式的具体实现方式以及它们的使用场景。

懒汉式单例

public class LazySingleton {

    private static LazySingleton instance;

    private LazySingleton() {
    }

    public synchronized static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

优点:懒加载,使用的以后才会进行加载,节省资源。

缺点:每次调用时都会进行synchronized线程同步,消耗很多不必要的资源,实际上只需要在第一次创建对象的时候才会需要线程同步。

注:synchronized,Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

双层校验锁单例

public class DoubleCheckSingleton {
    private static DoubleCheckSingleton instance;

    private DoubleCheckSingleton() {
    }

    public static DoubleCheckSingleton getInstance() {
        //first check
        if (instance == null) {
            //double check
            synchronized (DoubleCheckSingleton.class) {
                instance = new DoubleCheckSingleton();
            }
        }
        return instance;
    }
}

优点:懒加载,同时由懒汉式单例扩展而来,只是在第一次创建实例的时候才会进行线程同步,节省了资源。

缺点:双层校验锁的方法由于Java内存模型的原因偶尔会失效。

我们来分析一下这个缺点。先来看看这句话"instance = new DoubleCheckSingleton()",这段代码最终会被编译成多条汇编指令,大致如下:

  1. 给DoubleCheckSingleton的实例分配内存空间
  2. 调用DoubleCheckSingleton()构造函数
  3. 将创建的instance对象指向分配的内存空间

假设有线程A和线程B,同时执行上述3条指令,由于JVM编译器允许处理器允许上述指令乱序执行,即上述第二条与第三条的顺序无法保证。想象以下这种情况:线程A异常执行过了第三条,此时
instance非空,线程B直接取走instance,因为instance虽然非空,但是并没有经过DoubleCheckSingleton()构造函数初始化,再使用时就会出错。

JDK1.5以后,通过volatile关键字来保证对象都是从主内存读取,来避免上述问题。

注:volatile,java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更加方便。如果
一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。

public class DoubleCheckSingleton {
    private volatile static DoubleCheckSingleton instance;

    private DoubleCheckSingleton() {
    }

    public static DoubleCheckSingleton getInstance() {
        //first check
        if (instance == null) {
            //double check
            synchronized (DoubleCheckSingleton.class) {
                instance = new DoubleCheckSingleton();
            }
        }
        return instance;
    }
}

容器单例

public class ContainerSingleton {

    private static HashMap<String, Object> map = new HashMap<>();

    private ContainerSingleton() {
    }

    public static void registerService(String key, Object service) {
        if (!map.containsKey(key)) {
            map.put(key, service);
        }
    }

    public static Object getService(String key) {
        return map.get(key);
    }
}

优点:将多个单例类对象注入到统一的管理类中,使用统一的接口进行管理,隐藏类具体实现,降低了耦合度。

静态内部类单例

public class InnerClassSingleton {
    
    private InnerClassSingleton() {
    }

    public static InnerClassSingleton getInstance() {
        return InnerClassSingletonHolder.instance;
    }

    private static class InnerClassSingletonHolder {
        private static final InnerClassSingleton instance = new InnerClassSingleton();
    }
}

优点:懒加载,无需线程同步,没有性能问题。

缺点:好像没什么缺点

枚举单例

public enum EnumSingleton {
    INSTANCE;
}

当当当,是不是炒鸡简单,这也是它最大的优点。

优点:写法简单,枚举的创建都是就是线程安全的,这也省去了线程同步的问题。而且它还解决了上述几种写法都不能解决的问题:"反序列化会导致重新创建实例"。

为什么反序列化会导致重新创建实例?

通过序列化可以讲一个单例的实例写到磁盘,然后再读回来,从而有效的获取一个单例的实例,即使这个单例的构造函数是私有的,反序列化中类里有个钩子函数readResolve(),
该函数可以控制对象的反序列化。

所以我们在上述几种单例的实现种,如果要避免反序列化重新创建实例,需要加上以下代码:

private Object readReslove() throws ObjectStreamException {
    //将instance直接返回,不再重新生产实例
   return instance;
}

好了,5种单例的实现方式都讲完了,我们来总结一下,不管是哪种实现方式,要点都在于:

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

以上5种从性能、便利等角度考虑,推荐内部类单例和枚举单例。

模式实践

了解了单例的常见实现,我们来看一下常见的开源项目中都是如何使用单例的。

Android-Universal-Image-Loader:https://github.com/nostra13/Android-Universal-Image-Loader

双层校验锁单例

public class ImageLoader {
    private volatile static ImageLoader instance;

    /** Returns singleton class instance */
    public static ImageLoader getInstance() {
        if (instance == null) {
            synchronized (ImageLoader.class) {
                if (instance == null) {
                    instance = new ImageLoader();
                }
            }
        }
        return instance;
    }
}

相关文章

  • Android系统编程思想篇:单例模式

    Android系统编程思想篇:单例模式 关于作者 郭孝星,程序员,吉他手,主要从事Android平台基础架构方面的...

  • 设计模式~单例-[Android_YangKe]

    单例模式: 众所周知android是基于java编程语言的一种操作系统, 所以只要懂java熟悉android a...

  • 【设计模式】单例模式

    单例模式 常用单例模式: 懒汉单例模式: 静态内部类单例模式: Android Application 中使用单例模式:

  • java并发编程,volatile关键字解析

    在研究android单例模式时,找到的一篇关于java并发编程的资料,里面比较详细地讲述了java并发编程的概念和...

  • Android 中的单例模式

    Android 中的单例模式 概述 单例模式算是我接触设计模式这种思想所学习的第一个设计模式。记得刚入行时面试,面...

  • Android系统编程思想篇:建造者模式

    Android系统编程思想篇:建造者模式 关于作者 郭孝星,程序员,吉他手,主要从事Android平台基础架构方面...

  • ios-设计模式-单例

    1)单例是一种编程思想,一个设计模式,与语言无关在采用了单例对象的应用程序中,需要单例类自行提供实例化单例对象,不...

  • iOS-单例模式

    swift的单例设计模式 OC的单例设计模式 新学习一种单例思想

  • Android 单例模式学习

    android的单例模式学习 1 饿汉单例模式 优点 : 缺点 : 2.0 懒汉模式 优点 : 缺点 : 2.1 ...

  • 编程设计模式01——单例模式

    1.单例模式定义在Android中的使用 单例模式在项目中唯一持久存在的模式,而且为整个系统提供这个实例。处理一些...

网友评论

      本文标题: Android系统编程思想篇:单例模式

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