Android框架设计模式(五)——Singleton Method
一、单例模式介绍
什么是单例模式
单例模式就是在整个全局中(无论是单线程还是多线程),该对象只存在一个实例,而且只应该存在一个实例,没有副本(副本的制作需要花时间和空间资源)。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,同时该对象需要协调系统整体的行为,单例模式是最好的解决方案。单例模式相当于只有一个入口的系统,使得所有想要获取该系统资源的对象都要经过该入口。
不管是在单线程应用还是多线程并发的应用,单例模式的使用都是一样的,只是在多线程并发的情况下,对于单例模式的实现方式需要加同步管理机制。
单例模式UML图
单例模式应用场景
确保某个类有且只有一个对象,避免产生多个对象消耗过多的资源(时间、空间),或者某种类型的对象只应该有一个的特殊情况。同时,使用单例模式能够体现共享和同步的思想,因为单例就是全局的意思,全局即共享,它需要协调系统的整体行为。因此,使用单例模式往往是与同步分不开的。
总的来说有下面三个需求的都可以使用单例模式:
(1)需要保证一致性,即间断性的操作时,每一次的操作结果会保留,下一次继续从上一次的间断点开始(I/O流,数据库链接、缓冲区等)。
(2)在逻辑上只能有一个实例(比如:某个具体的动物是独一无二的)
(3)频繁的创建和使用,而且创建消耗的资源较大。(I/O流、数据库链接)
例如:
I/O流操作
对于I/O流操作来说,创建过程复杂而且消耗很多资源。输入流只是一个字符流通渠道,对于一个文件流来说多个对象并没有实际意义,而且它需要保证一致性,多个对象只会加重冗余和开销。比如说:创建某一个文件的输入流,那么该输入流的任务就是将文件从外存读入内存当中而已,如果已经读到了一般,然后你关闭输入流;然后重新打开输入流,这时候如果获取的是一个新的输入流的话,因为是另外的一个输入流对象,那么他的指针和其他信息就与之前不一致,那么一切又将重新开始。如果你要让他保持一致性,那么要额外花费更多的开销在将之前的信息复制到现有的对象上,因此倒不如直接就是单例模式。
数据库连接
对于数据库来说,一个应用甚至多个应用都可以只对应一个数据库。同时,对于数据库访问来说,需要有一个连接池管理,以及同步管理来限制链接的数量和数据的同步。如果重新创建了一个数据库连接对象,那么当前创建的对象链接信息将与之前的信息不一致,造成程序隐患或者崩溃(如果要使得一致,那么又要重新对该对象进行资源初始化)。同样还是要保证一致性,因此单例在这里正好可以限制程序实现一个连接池,以及同步的机制,节省资源和时间。
上面的两种对象的创建都需要消耗内存和时间,而且由于他们需要保证前后一致性,因此也只应该有一个实例。
二、单例模式的实现
实现单例模式主要有如下几个关键点:
(1)构造函数不对外开放,一般为private
(2)通过一个静态方法或者枚举返回单例对象
(3)确保单例类的对象有且只有一个,尤其是在多线程的环境下
(4)确保单例类对象在反序列化时不会重新构建对象
上述关键点中,(1)、(2)两点容易实现,(3)、(4)比较麻烦,许多单例模式的实现都是在单线程下的,多线程下的单例模式就需要加上同步机制,而当需要把对象刻到磁盘上存放时,就会牵扯到反序列化的问题。因此单例模式在(3)、(4)的应用情况下需要特别处理。下面介绍几种实现单例模式的方式,他们有些是线程不安全的,有些是线程安全的;对于反序列化重构对象,只有枚举可以防止。
注意:对于反序列化,系统提供一个方法让程序猿控制对象的反序列化。因此,对于反序列化我们可以自己覆盖该方法进行处理。
几种实现方式
懒汉1(线程不安全)
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
该方法在多线程环境下无法实现单例,无法防止反序列化重新构建对象。
优点:最为简单;之所以称为懒汉,是因为它把单例初始化延迟到第一次调用
getInstance方法上。
缺点:线程不安全
懒汉2(线程安全)
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
使用synchronized关键字修饰getInstance方法,这样可以保证一般情况下单例对象的唯一性,但是会产生另一个问题:即使已经创建了单例,每次调用getInstance方法还是会进行同步(同步资源竞争处理)。这样浪费了不必要的资源,同时也将单例模式节省资源的性能降低。这是懒汉模式(线程安全)的一大弊病。
优点:线程安全
缺点:99%的同步是多与的
双重校检锁(线程安全)
public class Singleton1 {
private static Singleton1 instance = null;
private static Object synObj = new Object();
private Singleton1(){
}
public static Singleton1 getInstance(){
if (instance == null) {//先判断有没有实例化
synchronized(synObj){//如果没有被实例化就请求锁
if (instance == null) {//得到锁之后,再次判断是否已经被先前获得锁的对象实例化
return instance = new Singleton1();
}
}
}
return instance;
}
}
双重校检锁,把懒汉模式的弊病避免了,它首先判断是否已经有实例,然后再去竞争锁,竞争到锁了之后再一次判断,这样就可以避免在对象已经被实例化的情况下参与锁的竞争。
优点:单例唯一,线程安全
缺点:JDK1.6以上;第一次加载比较慢;偶尔会失败(双重检查锁定失效)
饿汉(静态成员变量)
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
使用静态成员变量只创建一次的特性实现单例模式,静态成员变量只创建一次这个特性是由classLoader管理,它可以保证该成员在整个全局只被初始化一次。
优点:代码简洁,不需要同步锁
缺点:之所以称为饿汉,是因为只要编译器一看到该类就会初始化单例,无法达到
延迟加载的目的。
静态内部类
package designmodle.singleton;
/**
* @author David
*
*/
public class Singleton3 {
private static class SingletonHolder{
private static final Singleton3 instance = new Singleton3();
}
private Singleton3(){}
public static Singleton3 getInstance(){
return SingletonHolder.instance;
}
}
与饿汉(静态成员变量)方法同样是使用classLoader机制,比饿汉好的地方在于它能够延迟加载,当第一次加载Singleton类时并不会初始化instance,只有在第一次调用getInstance方法时才会导致SingleHolder被加载,同时instance被初始化。
优点:线程安全,对象唯一性,延迟实例化
缺点:暂无
枚举(线程安全,且防反序列化)
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
一看就知道,如此简单!枚举是最简单的实现单例模式的方法,而且最重要的是枚举默认是线程安全的,同时,还可以防止反序列化!
枚举的这种方式很少有人使用,但是相当之简单!而且完全符合单例模式的全部关键要素。
优点:实例唯一、线程安全、防止反序列化重构、简单,简单,简单!
缺点:JDK1.5以上
容器实现单例模式
如果程序中有许多单例类别,那么