设计模式-单例模式

单例模式

单利模式在众多设计模式中应用比较广泛的,使用起来也比较方便和简单,但是他有几种写法,每种写法的性能优劣,也限制某些单利模式的使用场景。

1.定义

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

实现单例模式关键点:

构造函数不对外开发,一般为private。
通过一个静态方法或者枚举类型返回单lie对象。
确保单例类的对象在反序列化时不会重新构建对象。

2.几种写法

  • (1)饿汉模式
public class Ceo {
    private final static Ceo INSTANCE = new Ceo;

    private Ceo() {
    }

    public static Ceo getInstance() {
        return INSTANCE;
    }
}

很少会按照上面的写法,这种写法在声明静态类时就初始化了,有时候可能你并不想使用,岂不是浪费内存呢,一般会按照下面的方式来实现单例模式。

  • (2)一般的单例模式
public class Ceo {
    private static Ceo INSTANCE;

    private Ceo() {

    }

    public static Ceo getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Ceo();
        }
        return INSTANCE;
    }
}

这种方式的好处在于,在你使用的使用才进行初始化,从而可以解决内存资源。

  • (3)懒汉式单例模式

在多线程的情况下,你可以用下面的方式来实现单例模式,和一般的单例模式唯一的区别就是多了关键字synchronized,目的是为了保证多线程下对象的唯一性。 相比较第一个中,加上synchronized关键字,实现线程同步。

public class Ceo {
    private static Ceo INSTANCE;

    private Ceo() {

    }

    public static synchronized Ceo getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Ceo();
        }

        return INSTANCE;
    }
}

细心的同学会发现,不管你是第一次调用还是第n次调用,都会线程同步,线程同步时很耗时的,为了进一步解决这个问题。你可以采用下面的方式来实现单例模式。

  • (4)Double Check Lock(DCL)模式
public class Ceo {
    private static Ceo INSTANCE;

    private Ceo() {

    }

    public static Ceo getInstance() {
        if (INSTANCE == null) {
            synchronized(Ceo.class){
                 if (INSTANCE == null) {
                        INSTANCE = new Ceo();
                 }
            }
        }

        return INSTANCE;
    }
}

这种避免了前面几种缺点,既能在初始化时声明变量,也线程安全,这岂不是很好!但是首次调用时,初始化也是挺耗时的,所以能不能避免的呢。可以看看第七个单例模式的实现。

  • (5)静态内部类单例模式
    虽然DCL解决了资源消耗,多余同步,线程安全,但是有时候也会出现DCL失效的问题,你可以采用下面这种方式。
public class Ceo {

    private Ceo() {
    }

    public static synchronized Ceo getInstance() {
        return CEOHolder.ceo;
    }

    private static class CEOHolder {
        private static final Ceo ceo = new Ceo();
    }
}

这种形式,第一加载类时,并不会初始化Ceo,当真正第一次使用时,才会初始化。

  • (6)枚举单例模式

居然枚举类型也能实现单例模式,看看下面的代码是不是很简单,而且不仅简单,默认就是线程安全,在枚举类型中还可以有方法,如此简介。

public enum EnumSingleton {
    SINGLETON;

    public String doSome() {
        return "dosome";
    }
}
  • (7)容器式单例模式

可以通过一个容器来管理单例,可以在程序初始化时,将单例注入到容器中,这样既不会在首次使用时耗时,也隐藏了具体实现,只需要通过关键字获取相应的单例。

public class SingletonManager {
    private static HashMap<String, Object> mObjectHashMap = new HashMap();

    public static void registerObject(String key, Class object) {
        if (!mObjectHashMap.containsKey(key)) {
            try {
                mObjectHashMap.put(key, object.newInstance());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static Object getService(String key) {
        return mObjectHashMap.get(key);
    }

    public void onDestroy() {
        if (mObjectHashMap != null) {
            mObjectHashMap.clear();
            mObjectHashMap = null;
        }
    }
}

那么多单例模式的写法,在实际的应用的场景中,该如何选择呢?可以从下面两点考虑:

1.资源消耗:也就是说,尽可能少的进行线程同步,尽可能在使用时初始化,若是初始化也很耗时,则可以考虑容器型或者静态类型的。

2.线程安全:在多线程的场景下,使用线程安全的(4)。

3.单例模式泄露情况

  • 尽量使用Application Context。
    在 Android 中,单例模式写的不好容易出现,内存泄露,影响 app 的运行。在 Android 中,会用到Context,所以在单利模式中,Context尽量使用ApplicationContext,而非Activity/Fragment的Context,因为 Activity/Fragment 是有生命周期的,一旦他们他们走完生命周期,理应销毁,但是要被单例中应用 Activity/Fragment 中的Context,则他就不会被系统回收,容易出现内存的泄露。

  • WeakReference

若是比必须要用,也可用用weakReference方式来实现单例模式。

public class WeakSingleton {
    private static WeakSingleton weakSingleton = null;
    private static WeakReference<WeakSingleton> mWeakReference = null;

    private WeakSingleton() {
    }
    
    public static WeakSingleton getWeakSingleton() {
        if (weakSingleton == null) {
            weakSingleton = new WeakSingleton();
            mWeakReference = new WeakReference(weakSingleton);
        }
        return mWeakReference.get();
    }
}

WeakReference 每当GC 扫面一次都会将它进行回收,所以这种操作可以避免内存的泄露。

参考

Android源码设计模式解析与实战

上一篇:饿了么是阿里巴巴旗下的么?


下一篇:《纽约比加州时间早3个小时》 | 一首火遍美国的小诗