Java单例模式完全剖析
单例模式的类图
正如你在上图中所看到的,这不是单例模式的完整部分。此图中单例类保持了一个对唯一的单例实例的静态引用,并且会从静态getInstance()方法中返回对那个实例的引用。
例1显示了一个经典的单例模式的实现。
例1.经典的单例模式
?
- ?

- ?

例2两次调用ClassicSingleton.getInstance(),并且把返回的引用存储在成员变量中。方法testUnique()会检查这些引用看它们是否相同。例3是这个测试案例的输出:
例3.是这个测试案例的输出
?

- ?

- ?

除了在这个清单中的单例类强制使用了一个多线程错误处理,例4类似于例1中的单例类。在getInstance()方法第一次被调用时,调用这个方法的线程会休眠50毫秒以便另外的线程也有时间调用getInstance()并创建一个新的单例类实例。当休眠的线程觉醒时,它也会创建一个新的单例类实例,这样我们就有两个单例类实例。尽管例4是人为如此的,但它却模拟了第一个线程调用了getInstance()并在没有完成时被切换的真实情形。
例5测试了例4的单例类:
例5.失败的测试
?

- ?

- ?

在同步化getInstance()方法后,我们就可以得到例5的测试案例返回的下面的结果:
?

- ?

- ?

- ?

- ?

- ?

这段代码的基类首先创建出子类的实例,然后把它们存储在一个Map中。但是基类却得付出很高的代价因为你必须为每一个子类替换它的getInstance()方法。幸运的是我们可以使用反射处理这个问题。
使用反射
在例9的带注册表的单例类中,使用反射来实例化一个特殊的类的对象。与例8相对的是通过这种实现,Singleton.getInstance()方法不需要在每个被实现的子类中重写了。
例9 使用反射实例化单例类
?

关于单例类的注册表应该说明的是:它们应该被封装在它们自己的类中以便最大限度的进行复用。
封装注册表
例10列出了一个单例注册表类。
例10 一个SingletonRegistry类
?

注意我是把SingletonRegistry类作为一个单例模式实现的。我也通用化了这个注册表以便它能存储和取回任何类型的对象。例11显示了的Singleton类使用了这个注册表。
例11 使用了一个封装的注册表的Singleton类
?

- ?

- ?

- ?

前面这个测试案例序列化例12中的单例类,并且两次重构它。然后这个测试案例检查看是否被重构的单例类实例是同一个对象。下面是测试案例的输出:
?

- Buildfile:?build.xml? ???? ??init:? ???????[echo]?Build?20030422?(22-04-2003?11:32)? ???? ??compile:? ???? ??run-test-text:? ???????[java]?.INFO?main:?testing?singleton?serialization...? ???????[java]?.INFO?main:?testing?singleton?uniqueness...? ???????[java]?INFO?main:?checking?singletons?for?equality? ???? ???????[java]?Time:?0.1? ???? ???????[java]?OK?(2?tests)???
Buildfile: build.xml ??init: ???? [echo] Build 20030422 (22-04-2003 11:32) ??compile: ??run-test-text: ???? [java] .INFO main: testing singleton serialization... ???? [java] .INFO main: testing singleton uniqueness... ???? [java] INFO main: checking singletons for equality ?????? [java] Time: 0.1 ?????? [java] OK (2 tests)
单例模式结束语
单例模式简单却容易让人迷惑,特别是对于Java的开发者来说。在这篇文章中,作者演示了Java开发者在顾及多线程、类载入器和序列化情况如何实现单例模式。作者也展示了你怎样才能实现一个单例类的注册表,以便能够在运行期指定单例类。