单例模式

308 阅读4分钟

单例模式

定义

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

使用场景

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

实现单例模式的关键点

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());
      }
    }
  }
}

测试结果