美文网首页
单例模式

单例模式

作者: joychic | 来源:发表于2018-03-24 13:50 被阅读0次

    定义

    一个类有且仅有一个实例,并且自行实例化并向整个系统提供这个实例。

    使用场景

    确保某个类只有一个对象的场景,避免产生过多对象消耗过多的资源,或者某种类型的对象应该且只有一个。

    实现单例模式的关键点

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

    单例的几种写法

    • 饿汉式

    当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间。

    单例实例在类装载时就构建,因为虚拟机保证只会装载一次,在装载类的时候是不会发生并发的,所以饿汉式是线程安全的。

    public class SingleTon {
      private static SingleTon sSingleTon = new SingleTon();
    
      private SingleTon() {
      }
    
      public static SingleTon getInstance() {
        return sSingleTon;
      }
    }
    
    • 懒汉式

      懒汉模式的优点是只有在单例被使用时候才会实例化,在一定程度上节约了资源;缺点是第一次加载时需要及时进行实例化,并且每次调用getInstance()都会进行同步,务必会造成不必要的开销。

     public class SingleTon1 {
      private static SingleTon1 sSingleTon;
    
      private SingleTon1() {
      }
    
      public static synchronized SingleTon1 getInstance() {
        if (sSingleTon == null) {
          sSingleTon = new SingleTon1();
        }
        return sSingleTon;
      }
    }
    
    
    • 双重检验锁实现

    DCL(Double Check Lock )方式实现单例的优点是既能在需要时候才初始化单例,又能保证线程安全,并且单例对象初始化后调用getInstance()不进行同步锁。

    优点:资源利用率高,需要时候才加载,效率高。
    缺点:第一次加载反应稍慢,也由于java内存模型原因偶尔会失败,在高并发环境下有一定缺陷(发生概率很小)。

    使用volatile关键字,能保证并发下的可见性,如果不使用volatile,由于指令重排序的缘故,可能会导致DCL检验失效。

    public class SingleTon2 {
      private static volatile SingleTon2 sSingleTon2 = null;
    
      private SingleTon2() {
      }
    
      public static SingleTon2 getInstance() {
        if (sSingleTon2 == null) {
          synchronized (SingleTon2.class) {
            if (sSingleTon2 == null) {
              sSingleTon2 = new SingleTon2();
            }
          }
        }
    
        return sSingleTon2;
      }
    }
    
    • 静态内部类实现

    这是一种比较推荐的写法,第一次加载SingleTon3类时候并不会初始化sInstance,只有在第一次调用getInstance()方法时会导致虚拟机加载 SingletonHolder类,从而初始化sInstance。

    优点: 线程安全,延迟加载

    public class SingleTon3 {
      private SingleTon3() {
      }
    
      public static SingleTon3 getInstance() {
        return SingletonHolder.sInstance;
      }
    
      private static class SingletonHolder {
        private static final SingleTon3 sInstance = new SingleTon3();
      }
    }
    
    • 容器实现

      容器实现的思路与饿汉式有异曲同工之妙,优点是方便管理多个单例对象;缺点也和饿汉式基本一致。

    public class SingleTonManager {
      private static Map<String, Object> sObjectMap = new HashMap<>();
    
      private SingleTonManager() {
      }
    
      public static void registerService(String key, Object o) {
        if (!sObjectMap.containsKey(key)) {
          sObjectMap.put(key, o);
        }
      }
    
      public static Object getService(String key) {
        return sObjectMap.get(key);
      }
    }
    
    • 枚举实现

    Android中不推荐使用枚举类,因为它占用更多的内存,取而代之是推荐使用@IntDef,@StringDef之类的注解来代替枚举类。

    • 枚举类型继承自java.lang.Enum,并自动添加了values和valueOf方法。
    • 每个枚举常量是一个静态常量字段,使用内部类实现,该内部类继承了枚举类。
    • 所有枚举常量都通过静态代码块来进行初始化,即在类加载期间就初始化。
    • enum除了编译时会自动生成private的构造函数外,还会生成一些额外的代码块,而且这些代码块基本都是static的,这样务必会占用更多内存。
    public enum SingletonEnum {
      INSTANCE;
    
      private String tagName;
      public void setTag(String tagName) {
        this.tagName = tagName;
      }
    
      public String getTag() {
        return tagName;
      }
    
      @Override public String toString() {
        return "SingletonEnum{" + "tagName='" + tagName + '\'' + '}';
      }
    }
    
    
    • 测试类
    public class Test {
    
      public static void main(String[] args) {
    
        Show show = new Show();
    
        SingleTon singleTon = SingleTon.getInstance();
        SingleTon singleTon_ = SingleTon.getInstance();
    
        SingleTon1 singleTon1 = SingleTon1.getInstance();
        SingleTon1 singleTon1_ = SingleTon1.getInstance();
    
        SingleTon2 singleTon2 = SingleTon2.getInstance();
        SingleTon2 singleTon2_ = SingleTon2.getInstance();
    
        SingleTon3 singleTon3 = SingleTon3.getInstance();
        SingleTon3 singleTon3_ = SingleTon3.getInstance();
    
        SingleTonManager.registerService("test", singleTon);
        SingleTonManager.registerService("test1", singleTon1);
        SingleTonManager.registerService("test2", singleTon2);
        SingleTonManager.registerService("test3", singleTon3);
    
        SingleTon singleTonx = (SingleTon) SingleTonManager.getService("test");
        SingleTon1 singleTon1x = (SingleTon1) SingleTonManager.getService("test1");
        SingleTon2 singleTon2x = (SingleTon2) SingleTonManager.getService("test2");
        SingleTon3 singleTon3x = (SingleTon3) SingleTonManager.getService("test3");
    
        SingletonEnum singletonEnum = SingletonEnum.INSTANCE;
        singletonEnum.setTag("singletonEnum");
        SingletonEnum singletonEnum2 = SingletonEnum.INSTANCE;
    
        UnSingleTon unSingleTon = new UnSingleTon();
        UnSingleTon unSingleTon1 = new UnSingleTon();
    
        show.add(singleTon);
        show.add(singleTon_);
        show.add(singleTonx);
        show.add(singleTon1);
        show.add(singleTon1_);
        show.add(singleTon1x);
        show.add(singleTon2);
        show.add(singleTon2_);
        show.add(singleTon2x);
        show.add(singleTon3);
        show.add(singleTon3_);
        show.add(singleTon3x);
    
        show.add(singletonEnum);
        show.add(singletonEnum2);
        show.add(unSingleTon);
        show.add(unSingleTon1);
    
        show.showAll();
      }
    
      private static class Show {
        private List<Object> mObjectList = new ArrayList<>();
    
        public void add(Object o) {
          mObjectList.add(o);
        }
    
        public void showAll() {
          for (Object o : mObjectList) {
            System.out.println("Obj : " + o.toString());
          }
        }
      }
    }
    
    

    测试结果

    image.png

    常见错误

    如果需要在单例中传递一些必要的参数,比如Context对象,这时候需要谨慎点。比如将Activity的context对象通过getInstance()方法传递进去,会不会导致内存泄露?这点是需要考虑到的。
    在Android中,如果要传递Context对象,可以考虑使用Application的Context对象。

    相关文章

      网友评论

          本文标题:单例模式

          本文链接:https://www.haomeiwen.com/subject/scsqcftx.html