单例模式
单利模式在众多设计模式中应用比较广泛的,使用起来也比较方便和简单,但是他有几种写法,每种写法的性能优劣,也限制某些单利模式的使用场景。
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源码设计模式解析与实战