设计模式目录:
五、设计模式之单件(例)模式详解
附录A 03:设计模式之责任链模式
用来创建独一无二的,只能有一个实例的对象的入场券。
确保一个类只有一个实例,并提供一个全局访问点。
单件模式的类图可以说是所有设计模式的类图中最简单的,其类图上只有一个类。尽管从类设计的视角来说它很简单,但是实现上还是会遇到相当多的波折。
单件模式的类图:
下面来详细探究一下。
有一些对象其实我们只需要一个,比如:线程池(threadpool)、缓存(cache)、处理偏好设置和注册表(registry)的对象、日志对象等。事实上,这类对象只能有一个实例,如果有多个实例,就会导致许多问题产生。例如,程序异常、资源使用过量,或是不一致的结果。
许多时候,程序员互相约定是可以做到,但是有更好的办法,应该使用更好的这种。单件模式是经过时间考验的方法,可以确保只有一个实例会被创建。单件模式也给了我们一个全局的访问点,和全局变量一样方便,又没有全局变量的缺点。
例如:如果将对象赋值给一个全局变量,那么你必须在程序一开始就创建好对象(这里也和JVM的实现有关,有些JVM的实现是:在用到的时候才创建对象),假如这个对象非常耗费资源,而程序在这次的执行过程中又一直没有用到,不就浪费了?利用单间模式,有时我们可以控制它去实现:需要时才创建对象。
单件模式听起来简单,但如何保证一个对象只能被实例化一次,并能够解决相应存在的问题,并不是那么简单可以说出来。
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
我们用下图来分析阐释:
可以将上述代码用两个线程同时访问,会很容易看到,会被创建两个Singleton对象出来。
处理办法:
3.1.1、只要把getInstance()方法编程同步synchronized方法,多线程问题就可以轻易解决了
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public synchronized static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
这种方法的确可以轻易解决问题,但是同步会降低性能,从而又是一个性能问题。并且,这个单件对象的获取,只有在第一次获取时才会进行同步来避免多线程问题,如果按照上述同步方法,在接下来后的每次调用时,同步方法就是累赘。
小结:如果getInstance()的性能对于应用程序不是很关键,就直接用这种方法解决问题好了。你的程序能够接受这种性能上的损失,就用这种方式,但如果频繁被调用和使用,那么就要慎重考虑了。
3.1.2、使用“急切”创建实例,而不是用”延迟实例化“的做法
如果应用程序总是创建并使用单件实例,或者在创建和运行时方面的负担不太繁重,你可能想要急切(eagerly)创建此单件:
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return uniqueInstance;
}
}
利用这个做法,就可以在JVM加载这个类时,立刻创建唯一的单件实例,JVM保证在任何线程访问uniqueInstance静态变量之前,一定先创建此实例。
3.1.3、用“双重检查加锁”,在getInstance()中减少使用同步
利用双重检查加锁(double-checked locking),首先检查是否实例已经创建好了,如果尚未创建,“才”进行同步,这样一来,只有第一次会同步,这样就完美切合了要求。如下代码:(此种方式不适合jdk1.4及更早版本)
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
分析:
如果性能是你关心的重点,那么这个做法可以大大地减少getInstanc()的时间耗费。
红色第6条的解决办法之一:自行指定类加载器,并指定同一个类加载器
注:学习自《Head First 设计模式》