Android设计模式之单例模式

来源:互联网 发布:nba数据查询器 编辑:程序博客网 时间:2024/06/06 02:34

1、单例模式介绍

    单例模式是应用最广的模式之一,也是很多初级工程师唯一会使用的设计模式,在应用这个模式时,单例对象的类必须保证只有一个实例存在。如一个应用中,应该只有一个ImageLoader实例,这个ImageLoader中有包含线程池、缓存系统、网络请求等,很消耗资源,因此,这种不能自由构造多个对象的情况。’

2、定义

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

3、使用场景

    确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且只有一个。如要访问IO和数据库资源。

4、单例模式的关键点

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

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

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

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

5、单例模式的分类

  (1)懒汉模式

         懒汉模式时声明一个静态对象,并且在用户第一次使用getInstance时进行初始化

public class LazySingleton {//懒汉式单例模式//比较懒,在类加载时,不创建实例,因此类加载速度快,但运行时获取对象的速度慢private static LazySingleton intance = null;//静态私用成员,没有初始化private LazySingleton(){//私有构造函数}public static synchronized LazySingleton getInstance(){    //静态,同步,公开访问点if(intance == null){intance = new LazySingleton();}return intance;}}

注:getInstance()方法中添加了synchronized关键字,也就是getInstance是一个同步方法,这种保证了单例对象的唯一

        懒汉单例模式的优点是单例只有在使用时才会被实例化,在一定程度上节约了资源;缺点是第一次加载时需要及时进行实例化,反应稍慢,醉倒的问题是每次调用getInstance都是进行同步,造成不要的同步开销,不太建议使用。

    (2)饿汉模式

          在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快

在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快public class EagerSingleton {//饿汉单例模式//在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快private static EagerSingleton instance = new EagerSingleton();//静态私有成员,已初始化private EagerSingleton() {//私有构造函数}public static EagerSingleton getInstance(){    //静态,不用同步(类加载时已初始化,不会有多线程的问题)return instance;} }
      (3)Double Check Lock(DCK)实现单例

            DCL方式实现单例模式的优点是技能够在需要时才初始化单例,又能保证了线程安全,且单例对象初始化后调用getInstance不进行同步锁。

public class Singleton{private static Singleton sInstance=null;private Singleton(){//私有构造函数}public void doSomething(){System.out.println("do sth.");}public static Singleton getInstance(){if(sInstance==null){synchronized(Singleton.class){if(sInstance==null){sInstance=new Singleton();}}}return sInstance;}}

   可以看出getInstance()方法中对instance进行了两次判空,第一层判断主要是威力避免不必要的同步,第二层判断则是为了在null的情况下创建实例。这是什么意思呢?下面来分析一下:

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

            (1)给Singleton的实例分配内存

            (2)调用Singleton()的构造幻术,初始化成员字段

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

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

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

            DCL优点:资源利用率高,第一次执行getInstance时单例对象才会被实例化,效率高。缺点:第一次加载时反应稍慢,也由于Java内存模型的原因偶尔会失败。

      (4)静态内部类模式

public class Singleton  {  private Singleton(){ }    public static Singleton getInstance()  {  return Nested.instance;       }    //静态内部类   在第一次被引用时被加载  private static class SingletonHolder{  private static final Singleton instance = new Singleton();  }    public static void main(String args[]){  Singleton instance = Singleton.getInstance();  Singleton instance2 = Singleton.getInstance();  System.out.println(instance == instance2);  }  }  
              当第一次加载Singleton类是并不会初始化sInstance,只有在第一次打调用Singleton的getInstance()方法是才会导致sInstance被初始化,因此,第一次调用getInstance()方法会导致虚拟机加载SingletonHolder类,这种方法不仅能够确保线程安全,也能保证单例对象的唯一性,同事也延迟了单例的实例化,所以这是推进使用的单例模式实现方式。

总结:使用单例模式时要注意


原创粉丝点击