初识设计模式 chapter 05-单件模式

初识设计模式 chapter 05-单件模式


1 引言


单件模式(Singleton Pattern),又叫单例模式:用来创建独一无二的,只能有一个实例的对象的入场卷。单件模式的类图可以说是所有模式的类图中最简单的,事实上,它的类图上只有一个类!但是,可不要兴奋过头,尽管从类设计的视角来说它很简单,但是实现上还是会遇到相当多的波折。

2 正文


2.1 单件模式与全局变量


有些对象其实我们只需要一个,比方说: 线程池(Threadpool)、缓存(Cache)、对话框、处理偏好设置和注册表(Registry)的对象、日志对象,充当打印机、显卡等设备的驱动程序的对象。事实上,这类对象只能有一个实例,如果制造出多个实例,就会导致许多问题产生,例如:程序的行为异常、资源使用过量,或者是不一致的结果。
许多时候,的确通过程序员之间的约定就可以办到。但如果有更好的做法,大家应该都乐意接受。别忘了,就跟其他模式一样,单件模式是经得起时间考验的方法,可以确保只有一个实例会被创建。单件模式也给了我们一个全局的访问点,和全局变量一样方便,又没有全局变量的缺点。
举例来说:如果将对象赋值给一个全局变量,那么你必须在程序一开始就创建好对象(这其实和实现有关,有些JVM的实现是:在用到的时候才创建对象),对吧?万一这个对象非常消耗资源,而程序在这次的执行过程中又一直没有用到它,不久形成浪费了吗?稍后你会看到,利用单件模式,我们可以在需要时才创建对象。
利用静态类变量、静态方法和适当的访问修饰符(Access Modifier),你的确可以做到这一点。但是,不管使用哪一种方法,能够了解单件模式的运作方式仍然是很有趣的事。单件模式听起来简单,要做得对可不简单。不信问问你自己:要如何保证一个对象只能被实例化一次?

2.2 建立一个简单的单件模式


代码如下

//注意:此类非线程安全!
public class Singleton {
	
	//利用一个静态变量来记录Singleton类的唯一实例
	private static Singleton uniqueInstance;
 
	//把构造器声明为私有的,只有在Singleton类内才可以调用构造器
	private Singleton() {}
 
	//用getInstance()方法实例化对象,并返回这个实例
	public static Singleton getInstance() {
		/*
		 * 如果实例不存在,我们就利用私有的构造器产生一个Singleton实例并把它复制到uniqueInstance静态变量中。
		 * 请注意,如果我们不需要这个实例,它就永远不会产生。这就是“延迟实例化”(LazyInstance)。
		 */
		if (uniqueInstance == null) {
			uniqueInstance = new Singleton();
		}
		//当然Singleton是一个正常的类,具有一些其他用途的实例变量和方法
		return uniqueInstance;
	}
 
	// other useful methods here
}

大家可以思考下到底为什么这个是时非线程安全?

2.3 定义单件模式


单件模式:确保一个类只有一个实例,并提供一个全局访问点。

让我们更深入一点:
1、我们正在把某个类设计成自己管理的一个单独实例,同时也避免其他类再自行产生实例。要想取得单件实例,通过单件类全局访问点是唯一途径。
2、全局访问点:当你需要实例时,向类查询,他会返回单个实例。前面的例子利用延迟实例化的方式创建单件,这种做法对资源敏感的对象特别重要。

2.4 处理多线程


只要把getInstance()编程同步(Synchronized)方法,多线程灾难几乎就可以轻易解决了。通过增加Synchronized关键字到getInstance()方法中,我们迫使每个线程在进入这个方法之前,要先等候别的线程离开该方法。也就是说,不会有两个线程可以同时使用这个方法。
但是 同步会降低性能,这不又是另一个问题吗?说的很对,的确有一点不好。而比你所想象的还要严重一些的是: 只有第一次执行此方法时,才真正需要同步。换句话说,一旦设置好uniqueInstance变量,就不再需要同步这个方法了。之后每次调用这个方法,同步都是一种累赘

可以有一些选择:
1、如果getInstance()的性能对应用程序不是很关键,就什么都别做。

2、使用“急切”创建实例,而不用延迟实例化的做法。
如果应用程序总是创建并使用单件实例,或者在创建和运行时方面的负担不太繁重,你可能想要急切(eagerly)创建此单件,如下所示

public class Singleton {
	//在静态初始化器(static initializer)中创建单件。这段代码保证了线程安全(Thread Safe)。
	private static Singleton uniqueInstance = new Singleton();
 
	private Singleton() {}
 
	public static Singleton getInstance() {
		//已经有实例了,直接用它
		return uniqueInstance;
	}
}


3、用“双重检查加锁”(double-checked locking),首先检查是否实例已经创建了,如果尚未创建,“才“进行同步。这样依赖,只有第一次回同步,这正是我们想要的。
来看看代码:

public class Singleton {
	//volatile关键词确保:当uniqueInstance变量被初始化成Singleton实例时,多个线程正确地处理uniqueInstance变量
	private volatile static Singleton uniqueInstance;
 
	private Singleton() {}
 
	public static Singleton getInstance() {
		//检查实例,如果存在,就进入同步区块
		if (uniqueInstance == null) {
			synchronized (Singleton.class) {
				//进入区块后,再检查一次,如果仍是null,才创建实例。
				if (uniqueInstance == null) {
					uniqueInstance = new Singleton();
				}
			}
		}
		return uniqueInstance;
	}
}

3 本章小结


本章应该是已学习的设计模式中最简短的一章,也正如单例模式的使用频率一样,在程序中不会大量使用。
谨记:在Java中实现单件模式需要三个组成部分:私有的构造器、一个静态方法和一个静态变量。



















你可能感兴趣的:(设计模式,性能,线程安全)