当前位置: 首页 > 编程笔记 >

Android编程设计模式之单例模式实例详解

子车飞鹏
2023-03-14
本文向大家介绍Android编程设计模式之单例模式实例详解,包括了Android编程设计模式之单例模式实例详解的使用技巧和注意事项,需要的朋友参考一下

本文实例讲述了Android编程设计模式之单例模式。分享给大家供大家参考,具体如下:

一、介绍

单例模式是应用最广的模式之一,也可能是很多初级工程师唯一会使用的设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为。

二、定义

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

三、使用场景

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

四、实现方式

1、饿汉模式

示例代码:

/**
 * 饿汉模式
 */
public class Singleton {
  private static Singleton instance;
  private Singleton(){}
  public static Singleton getInstance(){
    if(instance == null){
      instance = new Singleton();
    }
    return instance;
  }
}

优点:延迟加载(需要的时候才去加载)

缺点:线程不安全,在多线程中很容易出现不同步的情况,如在数据库对象进行的频繁读写操作时。

2、懒汉模式

示例代码:

/**
 * 懒汉模式
 */
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已经被初始化(第一次调用的时候就会被初始化instance),每次调用getInstance方法都会进行同步,这样会消耗不必要的资源,这也是懒汉模式存在的最大问题。

优点:解决了线程不安全的问题。

缺点:第一次加载时需要及时进行实例化,反应稍慢,最大问题是每次调用getInstance都进行同步,造成不必要的同步开销。

补充:在Android源码中使用的该单例方法有:InputMethodManager,AccessibilityManager等都是使用这种单例模式

3、Double Check Lock(DCL)双重检查锁定

示例代码:

/**
 * 双重检查锁定(DCL)单例模式
 */
public class Singleton {
  private static Singleton instance;
  private Singleton(){}
  public static Singleton getInstance(){
    if(instance == null){
      synchronized (Singleton.class) {
        if(instance == null) {
          instance = new Singleton();
        }
      }
    }
    return instance;
  }
}

本程序的亮点自然在getInstance方法上,可以看到getInstance方法中对instance进行了两次判空:第一层判断主要是为了避免不必要的同步,第二层的判断则是为了在null的情况下创建实例。

假设线程A执行到instance = new Singleton()语句,这里看起来是一句代码,但实际上它并不是一个原子操作,这句代码最终会被编译成多条汇编指令,它大致做了3件事情:

(1)个Singleton的实例分配内存;

(2)调用Singleton()的构造函数,初始化成员字段;

(3)将instance对象指向分配的内存空间(此时instance就不是null了)。

但是,由于java编译器允许处理器乱序执行,以及JDK1.5之前JMM(Java Memory Model,即Java内存模型)中Cache、寄存器到主内存回写顺序的规定,上面的第二和第三句的顺序是无法保证的。也就是说,执行顺序可能是1-2-3也可能是1-3-2。如果是后者,并且在3执行完毕、2未执行之前,被切换到线程B上,这时候instance因为已经在线程A内执行过了第三点,instance已经是非空了,所有,线程B直接取走了instance,再使用时就会出错,这就是DCL失效问题,而且这种难以跟踪难以重现的错误很可能会隐藏很久。

在JDK1.5之后,SUN官方已经注意到这种问题,调整了JVM,具体化了volatile关键字,因此,如果JDK是1.5或之后的版本,只需要将instance的定义改成private volatile static Singleton instance就可以保证instance对象每次都是从主内存中读取,就可以使用DCL的写法来完成单例模式。当然,volatile或多或少也会影响到性能,但考虑到程序的正确性,牺牲这点性能还是值得的。

优点:资源利用率高,第一次执行getInstance时单例对象才会被实例化,效率高。在并发量不多,安全性不高的情况下或许能很完美运行单例模式

缺点:第一次加载时反应稍慢,也由于Java内存模型的原因偶尔会失败。在高并发环境下也有一定的缺陷,虽然发生概率很小。

补充:在android图像开源项目Android-Universal-Image-Loader (https://github.com/nostra13/Android-Universal-Image-Loader)中使用的是这种方式。
DCL模式是使用最多的单例实现方式,它能够在需要时才实例化单例对象,并且能够在绝大多数场景下保证单例对象的唯一性,除非你的代码在并发场景比较复杂或者低于JDK6版本下使用,否则,这种方式一般能够满足需要。

4、静态内部类单例模式

DCL虽然在一定程度上解决了资源消耗、多余的同步、线程安全等问题,但是,它还是在某些情况下出现失效的问题。这个问题被称为双重检查锁定(DCL)失效,在《Java并发编程实践》一书的最后谈到了这个问题,并指出这种“优化”是丑陋的,不赞成使用。而建议使用如下的代码替代:

示例代码:

/**
 * 静态内部类单例模式
 */
public class Singleton {
  private Singleton(){}
  public static Singleton getInstance(){
    return SingletonHolder.instance;
  }
  /**
   * 静态内部类
   * 延迟加载,减少内存开销
   */
  private static class SingletonHolder{
    private static final Singleton instance = new Singleton();
  }
}

当第一次加载Singleton类时并不会初始化instance,只有在第一次调用Singleton的getInstance方法时才会导致instance被初始化。因此,第一次调用getInstance方法会导致虚拟机加载SingletonHolder类,这种方式不仅能够确保线程安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化,所以这是推荐使用的单例模式实现方式。

优点:延迟加载,线程安全(java中class加载时互斥的),也减少了内存消耗

5、枚举单例

前面讲解了一些单例模式实现方式,但是,这些实现方式不是稍显麻烦就是会在某些情况下出现问题。

示例代码:

/**
 * 枚举单例模式
 */
public enum Singleton {
  /**
   * 1.从Java1.5开始支持;
   * 2.无偿提供序列化机制;
   * 3.绝对防止多次实例化,即使在面对复杂的序列化或者反射攻击的时候;
   */
  instance;
  private String others;
  Singleton() {
  }
  public String getOthers() {
    return others;
  }
  public void setOthers(String others) {
    this.others = others;
  }
}

写法简单是枚举单例最大的优点,枚举在Java中与普通的类是一样的,不仅能够有字段,还能有自己的方法。最重要的是默认枚举实例的创建是线程安全的,并且在任何情况下它都是一个单例。

为什么这么说呢?在上述的几种单例模式实现中,在一个情况下它们会出现重新创建对象的情况,那就是反序列化。

通过序列化可以将一个单例的实例对象写到磁盘,然后在读回来,从而有效的获得一个实例。即使构造函数是私有的,反序列化时依然可以通过特殊的途径去创建类的一个新的实例,相当于调用该类的构造函数。反序列化操作提供了一个特别的钩子函数,类中具有一个私有的、被实例化的方法readResolve(),这个方法可以让开发人员控制对象的反序列化。例如,上述几个示例中如果要杜绝单例对象在被反序列化时重新生成对象,那么必须加入如下方法:

private Object readResolve() throws ObjectStreamException {
  return instance;
}

也就是在readResolve方法中将instance对象返回,而不是默认的重新生成一个新的对象。而对于枚举,并不存在这个问题,因为即使反序列化它也不会重新生成新的实例。

优点:无偿提供序列化机制,绝对防止多次实例化,即使在面对复杂的序列化或者反射攻击的时候。

缺点:从Java1.5开始支持。

上面主要讲了单例模式5种创建方法,大家可以根据其优缺点进行个人实际项目中的使用。

更多关于Android相关内容感兴趣的读者可查看本站专题:《Android开发入门与进阶教程》、《Android调试技巧与常见问题解决方法汇总》、《Android基本组件用法总结》、《Android视图View技巧总结》、《Android布局layout技巧总结》及《Android控件用法总结》

希望本文所述对大家Android程序设计有所帮助。

 类似资料:
  • 本文向大家介绍JavaScript设计模式之单例模式实例,包括了JavaScript设计模式之单例模式实例的使用技巧和注意事项,需要的朋友参考一下 《Practical Common Lisp》的作者 Peter Seibel 曾说,如果你需要一种模式,那一定是哪里出了问题。他所说的问题是指因为语言的天生缺陷,不得不去寻求和总结一种通用的解决方案。 不管是弱类型或强类型,静态或动态语言,命令式或说

  • 介绍 从本章开始,我们会逐步介绍在JavaScript里使用的各种设计模式实现,在这里我不会过多地介绍模式本身的理论,而只会关注实现。OK,正式开始。 在传统开发工程师眼里,单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。在JavaScript里,单例作为一个命名空间提供者,从全局命名空间里提供一个唯

  • 本文向大家介绍C#设计模式之单例模式实例讲解,包括了C#设计模式之单例模式实例讲解的使用技巧和注意事项,需要的朋友参考一下 前言 最近开始花点心思研究下设计模式,主要还是让自己写的代码可重用性高、保证代码可靠性。所谓设计模式,我找了下定义:是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。毫无疑问,设计模式于己于他人于系统都是多赢的;设计模式使代码编制真正工程化;设计模式是软件工

  • 本文向大家介绍java  设计模式之单例模式,包括了java  设计模式之单例模式的使用技巧和注意事项,需要的朋友参考一下 java  设计模式之单例模式 前言: 在软件开发过程中常会有一些对象我们只需要一个,如:线程池(threadpool)、缓存(cache)、对话框、偏好设置等。这些对象如果制造出多个实例的话可能会导致一些不必要的麻烦,如:程序行为异常、资源使用过量等。这时单例模式就可以确保

  • 本文向大家介绍java设计模式之单例模式,包括了java设计模式之单例模式的使用技巧和注意事项,需要的朋友参考一下 单例模式也叫做单肩模式,也是一种创建型模式,是我们日常开发中最常使用的一种设计模式,经常被用来封装一些工具类,例如数据库连接等。 单例模式的定义: 单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例。即一个类

  • 本文向大家介绍Android 单例模式 Singleton 简单实例设计模式解析,包括了Android 单例模式 Singleton 简单实例设计模式解析的使用技巧和注意事项,需要的朋友参考一下 单例模式 Singleton 简单实例设计模式解析 前言 今天我来全面总结一下Android开发中最常用的设计模式 - 单例模式。 关于设计模式的介绍,可以看下我之前写的:1分钟全面了解“设计模式” 目录

  • 本文向大家介绍Python设计模式之简单工厂模式实例详解,包括了Python设计模式之简单工厂模式实例详解的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了Python设计模式之简单工厂模式。分享给大家供大家参考,具体如下: 简单工厂模式(Simple Factory Pattern):是通过专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类. 下面使用简单工厂模式实现一

  • 本文向大家介绍JavaScript设计模式之享元模式实例详解,包括了JavaScript设计模式之享元模式实例详解的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了JavaScript设计模式之享元模式。分享给大家供大家参考,具体如下: 通过两个例子的对比来凸显享元模式的特点:享元模式是一个为了提高性能(空间复杂度)的设计模式,享元模式可以避免大量非常相似类的开销。 第一实例,没有使用享元模