您的位置:首页 > 移动开发 > Android开发

android源码中单例模式学习笔记与分享之一

2016-10-20 18:10 316 查看
单例模式是应用最广的模式之一,也可能是很多初级工程师唯一会使用的设计模式。但是真的会使用吗?使用过程中遇到哪些坑了?单例模式最简单,的确是,它的概念只有二十多个字,不够一行。

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

单例模式的使用场景其实很简单,就是避免一个类产生多个对象,造成过多的消耗资源而导致性能降低,比如访问IO和数据库时,就要考虑使用单例模式。

基本上都知道什么时候该用单例模式,但是如何用好,如何用的没有问题,其实并不是那么简单的。之前网上讲述单例模式实现的方式最多的就是饥汉模式和饱汉模式,但是可以很负责人的讲,这个是错误的,或者说只是很小的一部分,因为提出这个概念的人根本没有考虑多线程情况下,所以这种说法本身就是不严谨的。下面就介绍下单例模式的几种实现方式:

第一种方式:

private static final TestSingleton mInstance = new TestSingleton();
private TestSingleton(){
}
public static TestSingleton getmInstance(){
return mInstance;
}
      这种方式没有什么不对的,确实能满足绝大部分需求,但是你发现这个单例对象是否被使用,这个对象都会存在于内存中,这就造成好大的内存浪费,如果这个类简单还好,如果很复杂,内容很多,那岂不是要为你的这个行为付出很大的性能代价,所以是不可取的。有人就会说,那我把实例化不放在声明处,直接放在获取方法里面,那便上是下面的第二种实现方式。

第二种方式:

private static TestSingleton mInstance = null;
private TestSingleton(){
}
public static synchronized TestSingleton getmInstance(){
if(mInstance == null) {
mInstance = new TestSingleton();
}
return mInstance;
}
发会现getmInstance()方法中添加synchronized关键字,这个是为了防止多线程下造成不同步而产生多个对象,尽管如此,但是还是有问题,会发现即使mInstance被初始化了,每次调用getmInstance方法都会进行同步,这样会消耗不必要的资源,这也是这个实现方式下最大的问题所在。总结下,这种模式估且叫懒汉单例模式,缺点是第一次加载时需要用时进行实例化,反应稍慢,最大的问题是每次都需要进行同步,造成不必要的同步开销,优点就是只有在使用时才会被实例化。

第三种方式叫DLC方式,是Double Check Lock的缩写:

public class TestSingleton {

private static TestSingleton sInstance;

private TestSingleton(){
}
//use DCL to realize the singleton
public static TestSingleton getInstance(){
if (sInstance == null){
synchronized (TestSingleton.class){
if (sInstance == null){
sInstance = new TestSingleton();
}
}
}
return sInstance;
}
}
这种方式确实不错,既能够在需要时才初始化单例,又能保证线程安全,且单例对象初始化后调用不进行同步锁。代码的亮点之处在于两次对sInstance进行判空:第一层判断主要是避免不必要的同步,第二层判断是为了在null下的情况下创建实例。DCL方式的优点:资源利用率高,第一次执行getInstance才会去实例化,效率高。尽管这样,但是还是存在问题,就是第一次加载反应稍慢,也会因为Java内存模型的原因偶尔会失败。

第四种方式:

public class TestSingleton {

private TestSingleton(){
}
public static TestSingleton getInstance(){
return SingletonHolder.sInstance;
}
private static class SingletonHolder{
private static final TestSingleton sInstance = new TestSingleton();
}

}
当第一次加载类的时候不会实例化单例对象,只有第一次调用getInstance方法时才会去实例化单例,这种方式不仅能够确保线程安全,也能够保证对象唯一性,同时也延迟了单例的实例化,所以这是推荐使用的单例模式实现方式。但是这个还是会存在一个问题,那就是当这个对象被反序列化后还是会生成单例对象,造成多个单例存在。但是这种问题在之前的所有方式都存在。
既然有问题,那肯定还是有最正确的方式,下面是第五种实现方式,叫做枚举单例,有人会说WTF,还有这么用的,的确有。而且代码非常简洁,而且避了上述四种方式存在的所有问题。

public enum TestSingleton1 {
SINGLETON_1
}
还有一种方式叫使用容器实现单例模式,在程序初始,将多个单例类型注入到一个统一的管理类中,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。

public class TestSingleton {
private static HashMap<String , Object> sMap = new HashMap<>();
private TestSingleton(){
}

public static void registerService(String key, Object instance){
if(!sMap.containsKey(key)){
sMap.put(key,instance);
}
}
public static Object getInstance(String key){
return sMap.get(key);
}

}


已经把所有实现单例的方法都说了一遍,是时候来一拨对单例模式的总结吧。实现单例模式主要有如下几个关键点:

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

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

3)确保单例类的对象有且只有一个,尤其是在多线程环境下;

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

已经将单例模式很透彻的描述完了,下篇将介绍android源码中单例模式的运用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: