设计模式之单例模式

单例模式的定义是它应该保证一个类仅有一个实例,同时这个类还必须提供一个访问该类的全局访问点。

实现单例模式有以下几个关键点:

(1)其构造函数不对外开放,一般为private;

(2)通过一个静态方法或者枚举返回单例类对象;

(3)确保单例类的对象有且只有一个,尤其要注意多线程的场景;

(4)确保单例类对象在反序列化时不会重新创建对象;

以下是单例模式的七种写法:

懒汉模式,线程不安全


public class SingletonPattern {

private static SingletonPattern singletonPattern = null;

private SingletonPattern() {
}

public static SingletonPattern getInstance() {
if (singletonPattern == null) {
singletonPattern = new SingletonPattern();
}
return singletonPattern;
}
}

类加载时只是申明实例,并未实例化,当调用getInstance方法,才进行实例化,但线程不安全,多个线程并发调用getInstance方法可能会导致创建多份相同的单例出来,解决的办法就是使用synchronized关键字。

懒汉模式,线程安全

public class SingletonPattern {

private static SingletonPattern singletonPattern = null;

public static SingletonPattern getInstance() {
synchronized (SingletonPattern.class) {
if (singletonPattern == null)
singletonPattern = new SingletonPattern();
}
return singletonPattern;
}
}

synchronized保证一个时间内只能有一个线程得到执行,另一个线程必须等待当前线程执行完才能执行,使得线程安全。缺点每次调用getInstance方法都进行同步,造成了不必要的同步开销。

饿汉模式


public class SingletonPattern {
//饿汉模式
private static final SingletonPattern singletonPattern = new SingletonPattern();

private SingletonPattern() {
}

public static SingletonPattern getInstance() {
return singletonPattern;
}
}

类加载时就已经进行实例化,类加载较慢,但获取对象速度快,线程安全。

双重校验DCL模式

 public class SingletonPattern {
private static volatile SingletonPattern singletonPattern = null;

private SingletonPattern() {
}

public static SingletonPattern getInstance() {
//第一层校验,为了避免不必要的同步
if (singletonPattern == null) {
synchronized (SingletonPattern.class) {
//第二层校验,实例null的情况下才创建
if (singletonPattern == null)
singletonPattern = new SingletonPattern();
}
}
return singletonPattern;
}
}

这里使用了volatile关键字,因为多个线程并发时初始化成员变量和对象实例化顺序可能会被打乱,这样就出错了,volatile可以禁止指令重排序。双重校验虽然在一定程度解决了资源的消耗和多余的同步,线程安全问题,但在某些情况还是会出现双重校验失效问题,即DCL失效。

静态内部类单例模式

 public class SingletonPattern {

private SingletonPattern() {
}

private static class SingletonPatternHolder {
private static final SingletonPattern singletonPattern = new SingletonPattern();
}

public static SingletonPattern getInstance() {
return SingletonPatternHolder.singletonPattern;
}
}

第一次调用getInstance方法时加载SingletonPatternHolder 并初始化singletonPattern,这样不仅能确保线程安全,也能保证SingletonPattern类的唯一性。

使用容器

SingletonManager

public class SingletonManager {
private SingletonManager() {
}

private static Map<String, Object> instanceMap = new HashMap<>();

public static void registerInstance(String key, Object instance) {
if (!instanceMap.containsKey(key)) {
instanceMap.put(key, instance);
}
}

public static Object getInstance(String key) {
return instanceMap.get(key);
}
}

SingletonPattern

public class SingletonPattern {
SingletonPattern() {
}

public void doSomething() {
Log.d("wxl", "doSomeing");
}
}

代码调用:

SingletonManager.registerInstance("SingletonPattern", new SingletonPattern());
SingletonPattern singletonPattern = (SingletonPattern) SingletonManager.getInstance("SingletonPattern");
singletonPattern.doSomething();

根据key获取对象对应类型的对象,隐藏了具体实现,降低了耦合度。

枚举单例模式

 public enum SingletonEnum {
INSTANCE;

public void doSomething() {
Log.d("wxl", "SingletonEnum doSomeing");
}
}

代码调用:

SingletonEnum.INSTANCE.doSomething();

更加简洁,线程安全,还能防止反序列化导致重新创建新的对象,而以上方法还需提供readResolve方法,防止反序列化一个序列化的实例时,会创建一个新的实例。枚举单例模式,我们可能使用的不是很多,但《Effective Java》一书推荐此方法,说“单元素的枚举类型已经成为实现Singleton的最佳方法”。不过Android使用enum之后的dex大小增加很多,运行时还会产生额外的内存占用,因此官方强烈建议不要在Android程序里面使用到enum,枚举单例缺点也很明显。

0%