单例模式

来源:互联网 发布:mac怎么调出所有应用 编辑:程序博客网 时间:2024/06/04 01:17

该系列博客为读《android源码设计模式解析与实战》的个人知识点记录。

单例模式

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

实现单例模式关键点

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

单例模式的实现方式

饿汉模式

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

这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。

懒汉模式

线程不安全

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

线程不安全,不能再并发的情况下使用

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

优点是可以懒加载,在一定程度上节约了资源;缺点是第一次加载需要及时进行实例化,反应稍慢,最大的问题是每次调用getInstance都进行同步,造成不必要的开销。一般不建议使用

Double CheckLock(DCL)

public class Singleton {      private volatile static Singleton sInstance;      private Singleton (){}      public static Singleton getSingleton() {      if (sInstance == null) {          synchronized (Singleton.class) {          if (sInstance == null) {              sInstance = new Singleton();          }          }      }      return sInstance;      }  } 

这种写法在getSingleton方法中对singleton进行了两次判空,第一次是为了不必要的同步,第二次是在null的情况下才创建实例。

dcl失效描述

假设线程A执行到sInstance = new Singleton(),看是一句代码,但并不是一个原子操作,这句代码最后被编译成多条汇编指令,大概做了3件事
1. 给Singleton的实例分配内存
2. 调用Singleton()的构造函数,初始化成员字段
3. 将sInstance对象指向分配的内存空间(此时sInstance就不是null了)

但由于java编译器允许处理器乱序执行,JDK1.5之前JVM中cache、寄存器到主内存回写顺序的规定,2,3的顺序无法保证,可能是123,也可能132,。如果是后者,并且在3执行完毕、2未执行之前,被切换到线程B,这时sInstance已经在A内执行过了3,sInstance已经非空,B直接取走sInstance再使用时就会出错。

JDK1.5之后,调整了jvm,具体化了volatile关键字。之后的版本只要改成上述sInstance的声明方式,就可以保证其对象每次都是从主内存中读取,volatile会或多或少的影响性能,但考虑到程序的正确性,牺牲这点性能还是值得的。

DCL优点是资源利用率高,第一次执行getInstance时单例对象才被实例化,效率高。缺点是第一次加载时反应稍慢一些,在高并发环境下也有一定的缺陷,虽然发生的概率很小。DCL虽然在一定程度解决了资源的消耗和多余的同步,线程安全等问题,但是他还是在某些情况会出现失效的问题,也就是DCL失效,在《Java并发编程实践》一书建议用静态内部类单例模式来替代DCL。

静态内部类单例模式

public class Singleton {     private Singleton(){    }      public static Singleton getInstance(){          return SingletonHolder.sInstance;      }      private static class SingletonHolder {          private static final Singleton sInstance = new Singleton();      }  } 

第一次加载Singleton类时并不会初始化sInstance,只有第一次调用getInstance方法时虚拟机加载SingletonHolder 并初始化sInstance ,这样不仅能确保线程安全也能保证Singleton类的唯一性,所以推荐使用静态内部类单例模式。

枚举单例

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

默认枚举实例的创建是线程安全的,并且在任何情况下都是单例,上述讲的几种单例模式实现中,有一种情况下他们会重新创建对象,那就是反序列化,将一个单例实例对象写到磁盘再读回来,从而获得了一个实例。反序列化操作提供了一个特别的钩子函数,类中具有一个私有的,被实例化的readResolve方法,这个方法可以让开发人员控制对象的反序列化。在上述的几个方法示例中如果要杜绝单例对象被反序列化是重新生成对象,就必须加入如下方法:

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

使用容器实现单例模式

public class SingletonManager {   private static Map<String, Object> objMap = new HashMap<String,Object>();  private Singleton() {   }  public static void registerService(String key, Objectinstance) {    if (!objMap.containsKey(key) ) {      objMap.put(key, instance) ;    }  }  public static ObjectgetService(String key) {    return objMap.get(key) ;  }}

将多种的单例类统一管理,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。

0 0
原创粉丝点击