单例模式

2018-03-24  本文已影响0人  joychic

定义

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

使用场景

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

实现单例模式的关键点

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

单例的几种写法

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

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

public class SingleTon {
  private static SingleTon sSingleTon = new SingleTon();

  private SingleTon() {
  }

  public static SingleTon getInstance() {
    return sSingleTon;
  }
}
 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之类的注解来代替枚举类。

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对象。

上一篇下一篇

猜你喜欢

热点阅读