设计模式之单例模式

191 阅读3分钟

使用场景

  • 只拥有一个全局对象,或者某种类型对象只应该存在一个
  • 避免产生多个对象耗费过多的资源

关键点

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

UML

说明:
  1. Singleton只有一个实例化对象,内部自己实现
  2. Client通过Singleton的getInstance方法获取实例对象

单例模型实例

公司里面的CEO为例,一个公司可以有几个VP,无数的员工,但是CEO只有一个

  • 基类,普通员工

      public class Staff {
    
          public void work(){
              //干活
          }
          
      }
    
  • 副总裁,继承自员工

      public class VP extends Staff {
      
          @Override
          public void work() {
              //管理下面的经理
          }
      }
    
  • 单例方式的恶汉模式,在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快

      public class CEO extends Staff {
      
          private static final CEO mCeo = new CEO();
      
          //构造函数私有
          public CEO() {
          }
      
          //共有的静态函数,对外暴露湖区单例对象的接口
          public static CEO getCeo(){
              return mCeo;
          }
      
          @Override
          public void work() {
              //管理VP
          }
      }
    
  • 公司类

      public class Company {
      
          private List<Staff> allStaffs = new ArrayList<Staff>();
      
          public void addStaff(Staff per) {
              allStaffs.add(per);
          }
      
          public void showAllStaffs() {
              for (Staff per : allStaffs) {
                  System.out.println("Obj : " + per.toString());
              }
          }
      
      }
    
  • 测试函数 public class Main {

          public static void main(String[] args) {
      
              Company company = new Company();
      
              //CEO对象只能通过getCeo函数获取
              CEO ceo1 = CEO.getCeo();
              CEO ceo2 = CEO.getCeo();
              company.addStaff(ceo1);
              company.addStaff(ceo2);
      
              //通过new创建VP对象
              VP vp1 = new VP();
              VP vp2 = new VP();
              company.addStaff(vp1);
              company.addStaff(vp2);
      
              //通过new创建Staff对象
              Staff staff1 = new Staff();
              Staff staff2 = new Staff();
              company.addStaff(staff1);
              company.addStaff(staff2);
      
              //通过打印,判断对象是否统一
              company.showAllStaffs();
          }
      }
    
  • 懒汉模式实现单例,在类加载时,不创建实例,因此类加载速度快,但运行时获取对象的速度慢

      public class CEO1 extends Staff {  
      
          private static CEO1 ceo1 = null;
      
          public CEO1() {
          }
      
          public static synchronized CEO1 getCeo1() {
              if (ceo1 == null) {
                  ceo1 = new CEO1();
              }
              return ceo1;
          }
      
          @Override
          public void work() {
              //管理VP
          }
      }
    

在一定程度上节约了资源,但在第一次加载时需要及时进行实例化,反应稍慢,最大的问题是在每次调用getCeo时候都进行同步,造成不必要的开支

  • Double CheckLock(DCL)--双重检查锁定,实现单例

      public class CEO2 extends Staff {
      
          private volatile static CEO2 mCeo2 = null;
      
          public CEO2() {}
      
          public static CEO2 getmCeo2(){
              if (mCeo2 == null){
                  synchronized (CEO2.class){
                      if (mCeo2 == null){
                          mCeo2 = new CEO2();
                      }
                  }
              }
              return mCeo2;
          }
      }
    

DCL优点是资源利用率高,缺点是由于Java编译器允许处理器乱序执行,以及JDK1.5之前的Java内存模型回写顺序规定,会造成DCL小几率失效问题

  • 静态内部类实现单例模式

      public class CEO3 extends Staff{
          public CEO3() {}
      
          public static CEO3 getCeo3(){
              return CEO3Holder.mCeo3;
          }
      
          /**
           * 静态内部类
           */
          private static class CEO3Holder{
              private static final CEO3 mCeo3 = new CEO3();
          }
      }
    

静态内部类不仅能够保证线程安全,也能够保证对象的唯一性

  • 枚举实例的创建是线程安全的,并且在任何情况下都是一个单例,即使反序列化(不用重写readResolve方法)也不会重新生成实例

      public enum CEO4Enum {
      
          CEO4;
      
          public void doSomething(){
              System.out.println("do sth.");
          }
      
      }
    
  • 使用容器实现单例模式

      public class CEOManager {
      
          private static Map<String, Object> mCeo = new HashMap<>();
      
          public static void registerService(String key, Object ceo){
              if (!mCeo.containsKey(key)){
                  mCeo.put(key,ceo);
              }
          }
      
          public static Object getService(String key){
              return mCeo.get(key);
          }
      
      }
    

在程序的初始化中,将多种单例类型注入到统一的管理类中,通过key获取对象对应类型的对象

扩展:Android源码分析之单例模式