频道栏目
首页 > 程序开发 > 移动开发 > Android > 正文
单例模式在Android中的应用
2017-03-20 09:33:58      个评论    来源:chengjiamei的专栏  
收藏   我要投稿

单例模式在Android中的应用,单例模式是应用最广的设计模式之一,在应用这种模式的时候,单例对象的类必须保证只有一个实例存在。许多时候,整个系统只需要拥有一个全局对象,这样有利于协调系统的整体行为。如一个应用中,应该只有ImageLoader实例,这个ImageLoader实例中又包含网络请求、缓存系统、线程池等,很耗资源,因此没有理由让他构造多个实例。这种不能自由构造对象的情况就是使用单例模式的场景。在Android系统中存在很多这种场景,比如最常用的context.getSystemService(),BluetoothAdapter.getDefaultAdapter()等等都是使用的单例模式。下面就列出几种单例模式的构建方式以及各种方式的优缺点。

1.懒汉模式

懒汉模式是申明一个静态变量,并且在用户第一次调用getInstance时进行初始化,懒汉模式实现如下:

public class Singleton {
	private static Singleton sInstance;
	private Singleton(){
	}
	public static synchronized Singleton getInstance(){
		if(sInstance == null){
			sInstance = new Singleton();
		}
		return sIntance;
	}
}


getIntance()方法中添加了synchronized关键字,也就是getInstance是一个同步方法,这就是上面所说的在多线程情况下保证单例对象唯一性的手段。但是这样存在一个问题,即使sInstance已经被初始化,每次调用getInstance都会进行同步,这样会消耗不必要的资源,这也是懒汉单例模式存在的最大问题。懒汉模式的最大优点是单例只有在使用的时候才会被实例化,在一定程度上节约了资源;缺点是第一次加载时需要及时初始化,反应稍慢,最大的问题是每次调用getInstance都进行同步,造成不必要的同步开销。一般不建议使用。

2.Double Check Lock(DCL)实现单例

DCL方式实现单例模式的优点是既能够在需要的时候才初始化单例又能保证线程安全,且单例对象的初始化后调用getInstance不进行同步锁。实现如下:

public class Singleton {
    private static Singleton sInstance;
    private Singleton(){}
    public static Singleton getInstance(){
        if(sInstance == null){
              synchronized(Singleton.class){
			if(sInstance == null){
				sInstance = new Singleton();
			}
		}
        }
	return sInstance;
    }
}

本程序的亮点在于getInstance()方法中对sIntance进行了两次非空判断:第一层主要是为了避免不必要的同步,第二层的判断则是为了在null的情况下创建实例。假设线程A执行到sInstance = new Singleton()的语句,这看起来是原子操作,但并不是,这句代码会被编译成多条汇编指令:给Singleton的实例分配内存,调用Singleton的构造函数,初始化成员字段,将sInstance对象指向分配的内存空间(此时的sInstance已经不是null了)。但是由于Java编译器允许处理器乱序执行,所以上述三个步骤的执行顺序无法得到保证。这就会导致DCL失效,而且这种难以跟踪重现的错误会隐藏很久。在JDK1.5后,只需要将sInstance的定义改成private volatile static Singleton sInstance = null就可以保证sInstance对象每次都是从内存中读取,就可以使用DCL的写法来完成单例模式。当然,volatile或多或少会影响到性能,但考虑到程序的正确性,还是值得的。DCL的优点是资源利用率高,第一次执行getInstance时单例才会被实例化,效率高。缺点是第一次加载时反应稍慢,也由于Java内存模型的原因会偶尔失败。在高并发环境下也有一定的缺陷,虽然发生概率较小。DCL模式是使用最多的单例实现方式,它能够在需要时才实例化单例,并且在绝大多数场景下保证单例对象的唯一性,除非你的代码在并发场景比较复杂或者低于JDK6的情况下使用,否则这种方式一定能够满足要求。

3.静态内部类单例模式

DCL虽然在一定程度上解决了资源消耗、多余的同步、线程安全等问题,但是它还是在某些情况下出现失效的问题。这个问题被才会被称为DCL双锁失效。静态内部类实现代码如下:

public class Singleton{
   	 private Singleton(){}
   	 public static Singleton getInstance(){
       		 return SingletonHolder.sInstance; 
    	}

   	private static class SingletonHolder {
	private static final Singleton sInstance = new Singleton();
	}

}
当第一次加载的时候Singleton类时,并不会初始化sInstance,只有第一次在调用Singleton的getS方法时才会导致sIn被初始化。因此第一次调用get方法会导致虚拟机加载SingletonHolder类,这种方法不但保证能够=线程安全们也能够,也能够保证单例对象的唯一性,同时也延迟了单例的实例化,。

4.枚举单例模式档
Java1.5版本起,单元素枚举实现单例模式成为最佳的方法,实现代码如下

class Resource{
}

public enum SomeThing {
    INSTANCE;
    private Resource instance;
    SomeThing() {
        instance = new Resource();
    }
    public Resource getInstance() {
        return instance;
    }
}

上面的类Resource是我们要应用单例模式的资源,具体可以表现为网络连接,mysql" target="_blank" title="MySQL知识库">数据库连接,线程池等等。
获取资源的方式很简单,只要 SomeThing.INSTANCE.getInstance() 即可获得所要实例。下面我们来看看单例是如何被保证的:
首先,在枚举中我们明确了构造方法限制为私有,在我们访问枚举实例时会执行构造方法,同时每个枚举实例都是static final类型的,也就表明只能被实例化一次。在调用构造方法时,我们的单例被实例化。
也就是说,因为enum中的实例被保证只会被实例化一次,所以我们的INSTANCE也被保证实例化一次。
可以看到,枚举实现单例还是比较简单的,除此之外我们再来看一下Enum这个类的声明:

public abstract class Enum>
        implements Comparable, Serializable

可以看到,枚举也提供了序列化机制。某些情况,比如我们要通过网络传输一个数据库连接的句柄,会提供很多帮助。

在上述的几种单例模式中,存在一种情况它们会出现重复创建对象的情况,那就是反序列化。通过序列化可以将一个单例的实例对象写到磁盘,然后再读回来,从而有效的获得一个实例。即使构造函数是私有的,反序列化时依然可以通过特殊的途径去创建类的一个新的实例,相当于调用该类的私有构造函数。反序列化操作提供了一个很特别的钩子函数,类中具有一个私有的、被实例化的方法readResolve,这个方法可以让开发人员控制对象的反序列化。上述几个示例中如果要杜绝对象下被反序列化中重新生成对象,那么需要加入如下方法:

private Object readResolve() throws ObjectStreamException{
    return sInstance;
}

也就是在readResolve()中将sInstance对象返回,而不是默认的重新生成一个新的对象。而对于枚举,并不存在这个问题,因为即使反序列化也不会生成新的实例。

5.使用容器实现单例模式

看看这种实现方式:

public class SingletonManager {
private static Map objMap = new HashMap();
private SingletonManager();
public static viud registerServuce(String key,Object instance){
     if(!objMap.containsKey(key){
         objMao.put(key,instance);
}
} 

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




在程序初始化的时候,将多种单例类型注入到一个统一的管理类中,在使用时根据Key获取对象对应类型的独享,这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体的实现,降低了耦合度。

总结:不管以哪种方式实现单例模式,它们的核心原理都是将构造函数私有化,并且通过静态方法获取一个唯一的实例,在获取这个的过程中必须保证线程安全、防止反序列化导致重新生成实例对象等问题。选择哪种方式取决于项目本身,如是否是复杂的并发环境、JDK版本过低、单例对象的资源消耗等。




点击复制链接 与好友分享!回本站首页
上一篇:Android开发技巧之xml tools属性详解
下一篇:Android开源框架PowerfulViewLibrary——PowerfulEditText的介绍和源码解析
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站