Android设计模式 -- 单例模式总结

前言

设计模式相关的推荐《敏捷软件开发 — 原则、模式与实践》作者 Robert C.Martin (Bob大叔)和《设计模式 — 可复用面向对象软件的基础》GOF 四人帮写的这两本书。前者对设计原则有详细描述,后者主要涉及经典的设计模式讲解。

单例模式

单例模式的特点:

  • 构造函数需要私有化,不允许在外部对其进行实例化,整个域中只允许有一个实例存在。

单例模式又有三种常用的建立方法,如下所示。

饿汉式

程序猿乐趣多多啊,取名都这么逗比。从名字上面看就容易理解既然饿了就吃东西,那么意味着一开始就将实例初始化了,饿汉嘛,要先吃东西。代码如下:

/**
* 单例模式之饿汉模式
*/
publc class SingleTonOfEager {
  	// 刚开始就进行初始化
	private static SingleTonOfEager sInstance = new SingleTonOfEager();
	
	private SingleTonOfEager(){
		// do nothing...
	}
  
  	// 仅能靠getInstance()获取实例
	public static SingleTonOfEager getInstance(){
		return sInstance;
	}
}
缺点
  • 占用资源

懒汉式

懒人都是伸手党,要用的时候才伸手取东西,所以使用的时候开始实例化。

  • 特点:需要使用同步锁,保证多线程情况下调用的安全性。
  • 缺点:有了同步锁,虽然第一次已经实例化了,但是之后的调用依然有同步锁保证线程安全的,那么会导致资源不必要的消耗和同步开销,不建议使用该模式
/**
* 单例模式之懒汉模式
*/
publc class SingletonOfLazy {

	private static SingletonOfLazy sInstance;
	
	private SingletonOfLazy(){
		// do nothing...
	}
  
  	// 仅能靠getInstance()获取实例
	public static synchronized SingletonOfLazy getInstance(){
		// 需要用到的时候再初始化
		if (sInstance == null) {
			sInstance = new SingletonOfLazy();
		}
		return sInstance;
	}
}

双重检查锁 DCL 单例

优点
  • 需要时才进行初始化;
  • 保证线程安全;
  • 已经初始化后的实例被调用时不需要再次进行同步锁,保证资源的高利用率。
/**
 * 单例模式之Double Check Lock模式
 */
public class SingletonOfDCL {
	
	private static SingletonOfDCL sInstance;

	private SingletonOfDCL(){
		// do nothing...
	}

	public static SingletonOfDCL getInstance() {
		if (sInstance == null) {
			synchronized (SingletonOfDCL.class) {
				if (sInstance == null) {
					sInstance = new SingletonOfDCL();
				}
			}
		}
		return sInstance;
	}
}
问题
  • 为什么要用两次非空判断呢?
  • 为什么有了 synchronized 关键字还需要 volatile 关键字呢?

为了保证不必要的同步判断,在 sInstance = new SingletonOfDCL() 这条语指令并不是原子指令,被编译器编译后会被编译成多条指令,大致做了 3 件事情:

  1. 为 SingletonOfDCL 实例分配内存;
  2. 调用 SingletonOfDCL 构造函数,初始化成员字段;
  3. 将 sInstance 对象指向分配的内存空间(此时 sInstance 就不是 null 了)。

问题 1:第一次判断为了避免每次都进行同步操作,synchronized 是重量级锁,每次都进行同步操作会影响性能;第二次判断是和 volatile 配合使用,由于 Java 中允许 CPU 乱序执行,对上面的执行顺序可能是 1-2-3,也可能是 1-3-2,那么多线程进行访问的时候,很可能某条线程在 2 之前先执行了 3,那么 sInstance 不为 null,其他线程对其进行访问时就会出现异常,而加上 volatile 的目的是为了防止虚拟机对其指令重排。因为 volatile 关键字有两个特性:

  • 保证可见性;
  • 防止指令重排;
缺点
  • 可能会发生DCL失效

DCL模式是使用最多的模式。这种方式一般能满足需求

静态内部类单例模式

DCL有可能会失效,所以不建议使用 DCL,看到这里又惊呆了,DCL都不建议使用,所以衍生出了下面这种模式:

public class SingletonOfInnerClass {

	private SingletonOfInnerClass(){}

	public static SingletonOfInnerClass getInstance(){
		return SingletonHolder.sInstance;
	}

	private static class SingletonHolder {
		private static final SingletonOfInnerClass sInstance = 
          new SingletonOfInnerClass();
	}
}
优点
  • 只在需要使用时进行初始化;
  • 初始化完成后不再进行二次初始化;
  • 保证线程安全。
原理

这里利用到 JVM 中的 clinit 方法的特性,在进行类初始化的过程中会执行 clinit 方法将静态语句块和静态变量加载到运行时方法区,该过程如果有多个线程去初始化同一个类,那么只会有一个线程能够进行类的初始化,别的线程会处于阻塞状态。因此静态内部类实现的单例模式在被多线程调用的时候,是线程安全的

参考
  • 《Android 源码设计模式》
  • 《深入理解 Java 虚拟机》
  • 《Java 并发编程实战》

你可能感兴趣的:(设计模式,设计模式总结,android,设计模式)