UML
软件工程
组织
北京火龙果软件工程技术中心
单例模式完全剖析(3)---- 探究简单却又使人迷惑的单例模式
kevin 翻译 选自:java研究组织
使用注册表
使用一个单例类注册表可以:
在运行期指定单例类
防止产生多个单例类子类的实例
在例8的单例类中,保持了一个通过类名进行注册的单例类注册表:
例8 带注册表的单例类
import
java.util.
HashMap
;
import
org.apache.log4j.
Logger
;
public
class
Singleton {
private
static
HashMap
map =
new
HashMap
();
private
static
Logger
logger =
Logger
.getRootLogger();
protected
Singleton() {
// Exists only to thwart instantiation
}
public
static
synchronized
Singleton getInstance(
String
classname) {
if
(classname ==
null
)
throw
new
IllegalArgumentException
(
"Illegal classname"
);
Singleton singleton = (Singleton)map.get(classname);
if
(singleton !=
null
) {
logger.info(
"got singleton from map: "
+ singleton);
return
singleton;
}
if
(classname.equals(
"SingeltonSubclass_One"
))
singleton =
new
SingletonSubclass_One();
else
if
(classname.equals(
"SingeltonSubclass_Two"
))
singleton =
new
SingletonSubclass_Two();
map.put(classname, singleton);
logger.info(
"created singleton: "
+ singleton);
return
singleton;
}
// Assume functionality follows that's attractive to inherit
}
这段代码的基类首先创建出子类的实例,然后把它们存储在一个Map中。但是基类却得付出很高的代价因为你必须为每一个子类替换它的getInstance()方法。幸运的是我们可以使用反射处理这个问题。
使用反射
在例9的带注册表的单例类中,使用反射来实例化一个特殊的类的对象。与例8相对的是通过这种实现,Singleton.getInstance()方法不需要在每个被实现的子类中重写了。
例9 使用反射实例化单例类
import
java.util.
HashMap
;
import
org.apache.log4j.
Logger
;
public
class
Singleton {
private
static
HashMap
map =
new
HashMap
();
private
static
Logger
logger =
Logger
.getRootLogger();
protected
Singleton() {
// Exists only to thwart instantiation
}
public
static
synchronized
Singleton getInstance(
String
classname) {
Singleton singleton = (Singleton)map.get(classname);
if
(singleton !=
null
) {
logger.info(
"got singleton from map: "
+ singleton);
return
singleton;
}
try
{
singleton = (Singleton)
Class
.forName(classname).newInstance();
}
catch
(
ClassNotFoundException
cnf) {
logger.fatal(
"Couldn't find class "
+ classname);
}
catch
(
InstantiationException
ie) {
logger.fatal(
"Couldn't instantiate an object of type "
+ classname);
}
catch
(
IllegalAccessException
ia) {
logger.fatal(
"Couldn't access class "
+ classname);
}
map.put(classname, singleton);
logger.info(
"created singleton: "
+ singleton);
return
singleton;
}
}
关于单例类的注册表应该说明的是:它们应该被封装在它们自己的类中以便最大限度的进行复用。
封装注册表
例10列出了一个单例注册表类。
例10 一个SingletonRegistry类
import
java.util.
HashMap
;
import
org.apache.log4j.
Logger
;
public
class
SingletonRegistry {
public
static
SingletonRegistry REGISTRY =
new
SingletonRegistry();
private
static
HashMap
map =
new
HashMap
();
private
static
Logger
logger =
Logger
.getRootLogger();
protected
SingletonRegistry() {
// Exists to defeat instantiation
}
public
static
synchronized
Object
getInstance(
String
classname) {
Object
singleton = map.get(classname);
if
(singleton !=
null
) {
return
singleton;
}
try
{
singleton =
Class
.forName(classname).newInstance();
logger.info(
"created singleton: "
+ singleton);
}
catch
(
ClassNotFoundException
cnf) {
logger.fatal(
"Couldn't find class "
+ classname);
}
catch
(
InstantiationException
ie) {
logger.fatal(
"Couldn't instantiate an object of type "
+
classname);
}
catch
(
IllegalAccessException
ia) {
logger.fatal(
"Couldn't access class "
+ classname);
}
map.put(classname, singleton);
return
singleton;
}
}
注意我是把SingletonRegistry类作为一个单例模式实现的。我也通用化了这个注册表以便它能存储和取回任何类型的对象。例11显示了的Singleton类使用了这个注册表。
例11 使用了一个封装的注册表的Singleton类
import
java.util.
HashMap
;
import
org.apache.log4j.
Logger
;
public
class
Singleton {
protected
Singleton() {
// Exists only to thwart instantiation.
}
public
static
Singleton getInstance() {
return
(Singleton)SingletonRegistry.REGISTRY.getInstance(classname);
}
}
上面的Singleton类使用那个注册表的唯一实例通过类名取得单例对象。
现在我们已经知道如何实现线程安全的单例类和如何使用一个注册表去在运行期指定单例类名,接着让我们考查一下如何安排类载入器和处理序列化。
Classloaders
在许多情况下,使用多个类载入器是很普通的--包括servlet容器--所以不管你在实现你的单例类时是多么小心你都最终可以得到多个单例类的实例。如果你想要确保你的单例类只被同一个的类载入器装入,那你就必须自己指定这个类载入器;例如:
private
static
Class
getClass(
String
classname)
throws
ClassNotFoundException
{
ClassLoader
classLoader =
Thread
.currentThread().getContextClassLoader();
if
(classLoader ==
null
)
classLoader = Singleton.
class
.getClassLoader();
return
(classLoader.loadClass(classname));
}
}
这个方法会尝试把当前的线程与那个类载入器相关联;如果classloader为null,这个方法会使用与装入单例类基类的那个类载入器。这个方法可以用Class.forName()代替。
序列化
如果你序列化一个单例类,然后两次重构它,那么你就会得到那个单例类的两个实例,除非你实现readResolve()方法,像下面这样:
例12 一个可序列化的单例类
import
org.apache.log4j.
Logger
;
public
class
Singleton
implements
java.io.
Serializable
{
public
static
Singleton INSTANCE =
new
Singleton();
protected
Singleton() {
// Exists only to thwart instantiation.
}
[b]
private
Object
readResolve() {
return
INSTANCE;
}[/b]}
上面的单例类实现从readResolve()方法中返回一个唯一的实例;这样无论Singleton类何时被重构,它都只会返回那个相同的单例类实例。
例13测试了例12的单例类:
例13 测试一个可序列化的单例类
import
java.io.*;
import
org.apache.log4j.
Logger
;
import
junit.framework.
Assert
;
import
junit.framework.
TestCase
;
public
class
SingletonTest
extends
TestCase
{
private
Singleton sone =
null
, stwo =
null
;
private
static
Logger
logger =
Logger
.getRootLogger();
public
SingletonTest(
String
name) {
super
(name);
}
public
void
setUp() {
sone = Singleton.INSTANCE;
stwo = Singleton.INSTANCE;
}
public
void
testSerialize() {
logger.info(
"testing singleton serialization..."
);
[b] writeSingleton();
Singleton s1 = readSingleton();
Singleton s2 = readSingleton();
Assert
.assertEquals(
true
, s1 == s2);[/b] }
private
void
writeSingleton() {
try
{
FileOutputStream
fos =
new
FileOutputStream
(
"serializedSingleton"
);
ObjectOutputStream
oos =
new
ObjectOutputStream
(fos);
Singleton s = Singleton.INSTANCE;
oos.writeObject(Singleton.INSTANCE);
oos.flush();
}
catch
(
NotSerializableException
se) {
logger.fatal(
"Not Serializable Exception: "
+ se.getMessage());
}
catch
(
IOException
iox) {
logger.fatal(
"IO Exception: "
+ iox.getMessage());
}
}
private
Singleton readSingleton() {
Singleton s =
null
;
try
{
FileInputStream
fis =
new
FileInputStream
(
"serializedSingleton"
);
ObjectInputStream
ois =
new
ObjectInputStream
(fis);
s = (Singleton)ois.readObject();
}
catch
(
ClassNotFoundException
cnf) {
logger.fatal(
"Class Not Found Exception: "
+ cnf.getMessage());
}
catch
(
NotSerializableException
se) {
logger.fatal(
"Not Serializable Exception: "
+ se.getMessage());
}
catch
(
IOException
iox) {
logger.fatal(
"IO Exception: "
+ iox.getMessage());
}
return
s;
}
public
void
testUnique() {
logger.info(
"testing singleton uniqueness..."
);
Singleton another =
new
Singleton();
logger.info(
"checking singletons for equality"
);
Assert
.assertEquals(
true
, sone == stwo);
}
}
前面这个测试案例序列化例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)
单例模式结束语
单例模式简单却容易让人迷惑,特别是对于Java的开发者来说。在这篇文章中,作者演示了Java开发者在顾及多线程、类载入器和序列化情况如何实现单例模式。作者也展示了你怎样才能实现一个单例类的注册表,以便能够在运行期指定单例类。
版权所有:UML软件工程组织