Android框架设计模式(五)——Singleton Method

  • 一单例模式介绍

    • 什么是单例模式
    • 单例模式UML图
    • 单例模式应用场景
  • 二单例模式的简单示例
    • 几种实现方式

      • 懒汉1线程不安全
      • 懒汉2线程安全
      • 双重校检锁线程安全
      • 饿汉静态成员变量
      • 静态内部类
      • 枚举线程安全且防反序列化
      • 容器实现单例模式
      • 如何防止反序列化重建对象
      • 实现方式小结
  • 三Android中单例模式范例
    • LayoutInflater
  • 四总结

一、单例模式介绍


什么是单例模式

单例模式就是在整个全局中(无论是单线程还是多线程),该对象只存在一个实例,而且只应该存在一个实例,没有副本(副本的制作需要花时间和空间资源)。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,同时该对象需要协调系统整体的行为,单例模式是最好的解决方案。单例模式相当于只有一个入口的系统,使得所有想要获取该系统资源的对象都要经过该入口。

不管是在单线程应用还是多线程并发的应用,单例模式的使用都是一样的,只是在多线程并发的情况下,对于单例模式的实现方式需要加同步管理机制。

单例模式UML图




单例模式应用场景

确保某个类有且只有一个对象,避免产生多个对象消耗过多的资源(时间、空间),或者某种类型的对象只应该有一个的特殊情况。同时,使用单例模式能够体现共享和同步的思想,因为单例就是全局的意思,全局即共享,它需要协调系统的整体行为。因此,使用单例模式往往是与同步分不开的。

例如:

  • I/O流操作

    对于I/O流操作来说,创建过程复杂而且消耗很多资源。同时,因为输入流只是一个渠道,而且它需要保证一致性(即当前操作之后的效果是会影响到以后的使用的),单纯从程序的实现上它并没有多样化的需求。比如说:创建某一个文件的输入流,那么该输入流的任务就是将文件从外存读入内存当中而已,你如果重新创建另一个输入流的话,那么一切又将重新开始,因为是另外的一个输入流对象,它的信息不一致了。如果你要让他保持一致性,那么要额外花费更多的开销,因此倒不如直接就是单例模式。

  • 数据库连接

    对于数据库来说,一个应用甚至多个应用都可以只对应一个数据库。同时,对于数据库访问来说,需要有一个连接池管理,以及同步管理来限制链接的数量和数据的同步。如果重新创建了一个数据库连接对象,那么当前创建的对象链接信息将与之前的信息不一致,造成程序隐患或者崩溃(如果要使得一致,那么又要重新对该对象进行资源初始化)。同样还是要保证一致性,因此单例在这里正好可以限制程序实现一个连接池,以及同步的机制,节省资源和时间。

上面的两种对象的创建都需要消耗内存和时间,而且由于他们需要保证前后一致性,因此也只应该有一个实例。


二、单例模式的简单示例



实现单例模式主要有如下几个关键点:

(1)构造函数不对外开放,一般为private

(2)通过一个静态方法或者枚举返回单例对象

(3)确保单例类的对象有且只有一个,尤其是在多线程的环境下

(4)确保单例类对象在反序列化时不会重新构建对象

上述关键点中,(1)、(2)两点容易实现,(3)、(4)比较麻烦,许多单例模式的实现都是在单线程下的,多线程下的单例模式就需要加上同步机制,而当需要把对象刻到磁盘上存放时,就会牵扯到反序列化的问题。因此单例模式在(3)、(4)的应用情况下需要特别处理。下面介绍几种实现单例模式的方式,他们有些是线程不安全的,有些是线程安全的;对于反序列化重构对象,只有枚举可以防止。

注意:对于反序列化,系统提供一个方法让程序猿控制对象的反序列化。因此,对于反序列化我们可以自己覆盖该方法进行处理。

几种实现方式


懒汉1(线程不安全)

public class Singleton {
 private static Singleton instance;
     private Singleton (){}
     public static Singleton getInstance() {
     if (instance == null) {
         instance = new Singleton();
     }
     return instance;
      }
 }  

该方法在多线程环境下无法实现单例,无法防止反序列化重新构建对象。

优点:最为简单;之所以称为懒汉,是因为它把单例初始化延迟到第一次调用

getInstance方法上。

缺点:线程不安全


懒汉2(线程安全)

 public class Singleton {
      private static Singleton instance;
      private Singleton (){}
      public static synchronized Singleton getInstance() {
      if (instance == null) {
          instance = new Singleton();
      }
      return instance;
      }
 }  

使用synchronized关键字修饰getInstance方法,这样可以保证一般情况下单例对象的唯一性,但是会产生另一个问题:即使已经创建了单例,每次调用getInstance方法还是会进行同步(同步资源竞争处理)。这样浪费了不必要的资源,同时也将单例模式节省资源的性能降低。这是懒汉模式(线程安全)的一大弊病。

优点:线程安全

缺点:99%的同步是多与的


双重校检锁(线程安全)

public class Singleton1 {
    private static Singleton1 instance = null;
    private static Object synObj = new Object();
    private Singleton1(){

    }
    public static Singleton1 getInstance(){
        if (instance == null) {//先判断有没有实例化
            synchronized(synObj){//如果没有被实例化就请求锁
                if (instance == null) {//得到锁之后,再次判断是否已经被先前获得锁的对象实例化
                    return instance = new Singleton1();
                }
            }
        }
        return instance;
    }
}

双重校检锁,把懒汉模式的弊病避免了,它首先判断是否已经有实例,然后再去竞争锁,竞争到锁了之后再一次判断,这样就可以避免在对象已经被实例化的情况下参与锁的竞争。

优点:单例唯一,线程安全

缺点:JDK1.6以上;第一次加载比较慢;偶尔会失败(双重检查锁定失效)


饿汉(静态成员变量)

public class Singleton {
      private static Singleton instance = new Singleton();
      private Singleton (){}
      public static Singleton getInstance() {
      return instance;
     }
 }

使用静态成员变量只创建一次的特性实现单例模式,静态成员变量只创建一次这个特性是由classLoader管理,它可以保证该成员在整个全局只被初始化一次。

优点:代码简洁,不需要同步锁

缺点:之所以称为饿汉,是因为只要编译器一看到该类就会初始化单例,无法达到

延迟加载的目的。


静态内部类

package designmodle.singleton;

/**
 * @author David
 *
 */
public class Singleton3 {
   private static class SingletonHolder{
       private static final Singleton3 instance = new Singleton3();
   }
   private Singleton3(){}

   public static Singleton3 getInstance(){
       return SingletonHolder.instance;
   }
}

与饿汉(静态成员变量)方法同样是使用classLoader机制,比饿汉好的地方在于它能够延迟加载,当第一次加载Singleton类时并不会初始化instance,只有在第一次调用getInstance方法时才会导致SingleHolder被加载,同时instance被初始化。

优点:线程安全,对象唯一性,延迟实例化

缺点:暂无


枚举(线程安全,且防反序列化)

public enum Singleton {
     INSTANCE;
     public void whateverMethod() {
     }
 }  

一看就知道,如此简单!枚举是最简单的实现单例模式的方法,而且最重要的是枚举默认是线程安全的,同时,还可以防止反序列化!

枚举的这种方式很少有人使用,但是相当之简单!而且完全符合单例模式的全部关键要素。

优点:实例唯一、线程安全、防止反序列化重构、简单,简单,简单!

缺点:JDK1.5以上


容器实现单例模式



如果程序中有许多单例类别,那么可能会需要一个容器类别进行管理,因此我们也可以通过容器进行实现多种类单例模式,同时使用了容器就能够使得获得单例接口统一,降低耦合度。

public class SingletonManager{
   private static Map<String,Object> objMap = new HashMap<String,Object>();
   private SingletonManager(){}
   public static void registerService(String key,Object instance){
      //判断是否包含了该单列对象,若没有包含则添加
      if(!objMap.containsKey(key)){
         objMap.put(key,instance);
      }
   }

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

这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户成本,也对用户隐藏了获取单例的具体实现(不用知道单例的类名和获取方法),降低了使用者与被使用单例类的耦合度。

优点:单例唯一,线程安全(指的是多线程情况下获得的是同样的单例,HashMap本身并不是线程安全的),单例类别管理,降低单例类别与用户之间的耦合。

缺点:暂无


如何防止反序列化重建对象

如何防止反序列化带来的问题,其实很简单。反序列化可以通过特殊的途径创建一个类的新实例而不管该类的构造函数的可见性。但是系统给我们提供了一个很特别的hook方法,是专门让开发人员控制对象的反序列化,该方法就是:readResolve(),我们可以在这个方法内部杜绝单例对象在背反序列化时重新生成对象。

加入方式很简单,举静态内部类的方式为例:

public class Singleton3 implements Serializable {
   private static class SingletonHolder{
       private static final Singleton3 instance = new Singleton3();
   }
   private Singleton3(){}

   public static Singleton3 getInstance(){
       return SingletonHolder.instance;
   }

   //添加这个hook函数,那么系统在反序列化的过程中就会通过该Hook方法得到原有的单例
   //而不是重新创建一个单例。
   private Object readResolve() throws ObjectStreamException{
       return SingletonHolder.instance;
   }
}

实现方式小结

无论哪种形式实现单例模式,它们的核心原理都是将构造函数私有化,并且通过静态方法获取一个唯一的实例,在这个获取的过程中保证线程安全、防止反序列化导致的对象重新生成等问题,选择哪种方式取决于项目本身。

(1)是否是复杂的并发环境

(2)JDK版本是否过低

(3)单例对象的资源消耗等


三、Android中单例模式范例



在Android系统中,我们经常会通过Context获取系统级别的服务,如WindowsManagerService、ActivityManagerService等,更加常用的是一个LayoutInflater的类,这些服务会在何时的时候以单例的形式注册在系统中,在我们需要的时候就通过Context的getSystemService(String name)获取,我们会看到Android中是使用容器的方式来多种服务的单例管理。我们以LayoutInflater为例来说明,平常我们使用LayoutInflater较为常见的地方是在ListView的getView()策略方法中:

LayoutInflater

@Override
public View getView(int position,View convertView, ViewGroup parent){
   ViewHolder holder = null;
   if(null == convertView){
       holder = new ViewHolder();
       convertView = LayoutInflater.from(mContext).inflate(mLayoutId,null);
       //代码省略
   }else{
       //代码省略
   }
   //代码省略
   return convertView;
}

我们是通过使用LayoutInflater.from(Context)来获取LayoutInflater服务的,下面看看LayoutInflater.from(Context)的实现:

public static LayoutInflater from(Context context){
  //获取系统的LayoutInflater服务
   LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   if(LayoutInflater == null){
      throw new AssertionError("LayoutInflater not found.");
   }
   return LayoutInflater;
}

我们从上面的代码可以看到,from(Context)方法是调用Context类的getSystemService(String key)方法。Context是一个抽象类别,那在Android中就一定还有它的实现类来操作Context的功能,在Application、Activity、Service中都会有一个Context对象,即Context的总个数为:Activity个数+Service个数+1(Application)。而ListView都是显示在Activity中的,因此我们可以查阅Activity的入口ActivityThread的main函数,对Activity的创建进行跟踪,可以发现最终在handleBindApplication方法中函数中创建了一个ContextImpl对象,而该对象就是Context的实现类。

public Application makeApplication(boolean forceDefaultAppClass,
        Instrumentation instrumentation) {
    if (mApplication != null) {
        return mApplication;
    }  

    Application app = null;  

    String appClass = mApplicationInfo.className;
    if (forceDefaultAppClass || (appClass == null)) {
        appClass = "android.app.Application";
    }  

    try {
        java.lang.ClassLoader cl = getClassLoader();
        //创建了ContextImpl类
        ContextImpl appContext = new ContextImpl();
        appContext.init(this, null, mActivityThread);
        app = mActivityThread.mInstrumentation.newApplication(
                cl, appClass, appContext);
        appContext.setOuterContext(app);
    } catch (Exception e) {
        if (!mActivityThread.mInstrumentation.onException(app, e)) {
            throw new RuntimeException(
                "Unable to instantiate application " + appClass
                + ": " + e.toString(), e);
        }
    }
    ...
}  

ContextImpl类:

class ContextImpl extends Context{
   //代码省略
   //ServiceFecher抓取器,通过getService获取各类服务对象
   static class ServiceFecher{
       //当前服务在容器中所处的位置,便于下面同步块中获取对应的服务
       int mContextCacheIndex = -1;
       //获取系统服务
       public Object getService(ContextImpl ctx){
         ArrayList<Object> cache = ctx.mServiceCache;
         Object service;
         //进行同步加锁控制
         synchronized(cache){
         if(cache.size()==0){
         for(int i=0;i< sNextPerContextServiceCacheIndex;i++){
         cache.add(null);
           }
          }else{
          //缓存非空,从缓存中获取Service对象
           sercice = cache.get(mContextCacheIndex);
           if(service != null){
             return service;
           }
          }
          //hook方法,当缓存中的Service为空时,重新创建
          service = createService(ctx);
          cache.set(mContextCacheIndex,service);
          return service;
         }
       }
       /**
       *hook方法,让子类重写该方法用以创建服务对象
       */
       public Object createService(ContextImpl ctx){
          throw new RuntimeException("Not implemented");
       }
   }
   //Service容器,作为各种单例的存放容器
   private static final HashMap<String,ServiceFetcher> STSTEM_SERVICE_MAP = new HashMap<String,ServiceFetcher>();
   //服务记录数指针,记录存放至容器的下一个服务位置
  private static int sNextPerContextServiceCacheIndex = 0;
  //注册服务
  private static void registerService(String serviceName,ServiceFetcher fetcher){
    if(!(fetcher instanceof StaticServiceFetcher)){
    //标记服务在容器中的位置
    fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
    }
    //将服务添加到Service容器中
    SYSTEM_SERVICE_MAP.put(serviceName,fetcher);
  }
   //静态语句块,第一次加载该类执行
   static {
       //代码省略
       //注册LayoutInflater Service
      registerService(LAYOUT_INFLATER_SERVICE,new ServiceFetcher(){
       //实现创建服务的hook方法
        public Object createService(ContextImpl ctx){
          return PolicyManager.makeNewLayoutInflater(
                 ctx.getOuterContext());
        }
      });
   }

   //。。。。。。。。。。。
   //代码省略

   //通过key获取对应的服务
   @Override
    public Object getSystemService(String name) {
        //根据name来获取服务选择器
     ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
     return  fetcher == null? null: fetcher.getService(this);

    }
}

从ContextImpl的部分代码中可以看到,ContextImpl是采用容的方式实现的单例模式。在虚拟机第一次加载该类时会注册各种ServiceFetcher,其中就包含了LayoutInflater Service。将这些服务以键值对的形式存储在一个HashMap中,用户使用时只需要根据key来获取对应的ServiceFetcher,然后通过ServiceFetcher对象的getService函数来获取具体的服务对象,当第一次获取时,会调用它的hook方法createService函数创建服务对象,然后将该对象缓存到一个列表中,下次再取的时候直接从缓存中获取,避免重复创建对象,从而达到单列的效果。这种模式下,系统的核心服务以单例的形式存在,减少了资源的消耗。


四、总结



单例模式是应用最广的模式之一,也是许多人最初接触并使用的设计模式。在我们的系统只需要一个全局的对象时,我们就可以使用它来节省系统资源并达到协调系统整体行为的目的。在应用这个模式的时候,要注意所说的4个关键点:构造函数私有化、一个单例、多线程安全、防止反序列化重建对象。

时间: 2024-10-26 14:15:56

Android框架设计模式(五)——Singleton Method的相关文章

Android框架设计模式(四)——Adapter Method

一适配器模式介绍 什么是适配器模式 定义 分类 适配器应用于什么场景 二Android框架中的适配器模式应用 ListViewBaseAdapter自定义View 通俗UML图 关键代码分析 ActivityBinderMediaPlayer 通俗UML图 关键代码分析 三适配器模式与其他模式的配合 适配器观察者模板策略组合 BaseAdapterListView自定义View 整体UML图 模式分析不同的视角决定 适配器观察者模板 Service Activity 自定义服务 整体UML图 模

Android与设计模式——模板方法(Template Method)模式

在阎宏博士的<JAVA与模式>一书中开头是这样描述模板方法(Template Method)模式的: 模板方法模式是类的行为模式.准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑.不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现.这就是模板方法模式的用意. 模板方法模式的结构 模板方法模式是所有模式中最为常见的几个模式之一,是基于继承的代码复用的基本技术. 模板方法模式需要开发抽象类和具体子类的设计师之间的协作

Android设计模式——单例模式(Singleton)

二十三种设计模式分为三大类: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组合模式.享元模式. 行为型模式,共十一种:策略模式.模板方法模式.观察者模式.迭代子模式.责任链模式.命令模式.备忘录模式.状态模式.访问者模式.中介者模式.解释器模式. 1 package com.example.main; 2 3 import android.app.Activity; 4 import

php设计模式——单例模式(Singleton)

二十三种设计模式分为三大类: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组合模式.享元模式. 行为型模式,共十一种:策略模式.模板方法模式.观察者模式.迭代子模式.责任链模式.命令模式.备忘录模式.状态模式.访问者模式.中介者模式.解释器模式. 谷歌的Android设备 华为的Android设备 IOS只属于苹果公司 IOS只属于苹果公司 1 <?php 2 3 /* 4 * php

Android常用设计模式(二)

继上一篇 Android常用设计模式(一)里认识了观察者,适配器,代理等三种模式,这一篇将会讲解以下三种模式: 工厂模式 单例模式 命令模式 1.工厂模式(Factory Pattern) 工厂模式分为简单工厂模式,工厂方法模式以及抽象工厂模式 简单工厂模式:一般情况下,提供一个方法,方法的参数是一个标志位,根据标志位来创建不同的对象,这样调用的时候只需要提供一个标志位就可以创建一个实现了接口的类. 工厂方法模式:将简单工厂模式的那个方法分开,不再是在工厂方法中根据标志位创建对象了.而是定义一个

转 分享我在阿里工作十年接触过Java框架设计模式

转 原文: 分享我在阿里工作十年接触过Java框架设计模式 一.前言 说起来设计模式,大家应该都耳熟能详,设计模式代表了软件设计的最佳实践,是经过不断总结提炼出来的代码设计经验的分类总结,这些模式或者可以简化代码,或者可以是代码逻辑开起来清晰,或者对功能扩展很方便…. 设计模式按照使用场景可以分为三大类:创建型模式(Creational Patterns).结构型模式(Structural Patterns).行为型模式(Behavioral Patterns). 创建型模式(Creationa

在Kotlin中使用注释处理Android框架 kapt

本教程介绍如何在 Kotlin 中使用依赖于注释处理的流行的 Android 框架和库. 在日常 Android 开发中,流行着数以千计的框架帮助我们提升开发效率. 使用 Kotlin 开发时仍然可以沿用这些框架,而且和使用 Java 同样简单. 本章教程将提供相关示例并重点介绍配置的差异. 教程以 Dagger. Butterknife. Data Binding. Auto-parcel 以及 DBFlow 为例(其它框架配置基本类似). 以上框架均基于注解处理方式工作:通过对代码注解自动生

App 组件化/模块化之路——Android 框架组件(Android Architecture Components)使用指南

面对越来越复杂的 App 需求,Google 官方发布了Android 框架组件库(Android Architecture Components ).为开发者更好的开发 App 提供了非常好的样本.这个框架里的组件是配合 Android 组件生命周期的,所以它能够很好的规避组件生命周期管理的问题.今天我们就来看看这个库的使用. 通用的框架准则 官方建议在架构 App 的时候遵循以下两个准则: 关注分离 其中早期开发 App 最常见的做法是在 Activity 或者 Fragment 中写了大量

Android系统的五种数据存储形式(一)

Android系统有五种数据存储形式,分别是文件存储.SP存储.数据库存储.contentprovider 内容提供者.网络存储.其中,前四个是本地存储.存储的类型包括简单文本.窗口状态存储.音频视频数据.XML注册文件的各种数据.各种存储形式的特点不尽相同,因此对于不同的数据类型有着固定的存储形式,本文为演示方便给出的案例基本相同,都是是采用账号登录来演示数据存储,保存账号和密码信息,下次登录时记住账号和密码.重在说明各种存储形式的原理. 文件存储: 以I/O流的形式把数据存入手机内存或SD卡