单例模式完全剖析(3)---- 探究简单却又使人迷惑的单例模式

原创 2003年11月12日 17:36:00


使用注册表


使用一个单例类注册表可以:
在运行期指定单例类
防止产生多个单例类子类的实例
在例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() {

}
public static synchronized Singleton getInstance(String classname) {
if(classname == null) throw new IllegalArgumentException();
Singleton singleton = (Singleton)map.get(classname);

if(singleton != null) {
logger.info( + singleton);
return singleton;
}
if(classname.equals())
singleton = new SingletonSubclass_One();
else if(classname.equals())
singleton = new SingletonSubclass_Two();

map.put(classname, singleton);
logger.info( + singleton);
return singleton;
}

}

这段代码的基类首先创建出子类的实例,然后把它们存储在一个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() {

}
public static synchronized Singleton getInstance(String classname) {
Singleton singleton = (Singleton)map.get(classname);

if(singleton != null) {
logger.info( + singleton);
return singleton;
}
try {
singleton = (Singleton)Class.forName(classname).newInstance();
}
catch(ClassNotFoundException cnf) {
logger.fatal( + classname);
}
catch(InstantiationException ie) {
logger.fatal( + classname);
}
catch(IllegalAccessException ia) {
logger.fatal( + classname);
}
map.put(classname, singleton);
logger.info( + 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() {

}
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( + singleton);
}
catch(ClassNotFoundException cnf) {
logger.fatal( + classname);
}
catch(InstantiationException ie) {
logger.fatal( +
classname);
}
catch(IllegalAccessException ia) {
logger.fatal( + 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() {

}
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() {

}
private Object readResolve() {
return INSTANCE;
}
}
上面的单例类实现从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();
[b] writeSingleton();
Singleton s1 = readSingleton();
Singleton s2 = readSingleton();
Assert.assertEquals(true, s1 == s2);[/b] }
private void writeSingleton() {
try {
FileOutputStream fos = new FileOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(fos);
Singleton s = Singleton.INSTANCE;

oos.writeObject(Singleton.INSTANCE);
oos.flush();
}
catch(NotSerializableException se) {
logger.fatal( + se.getMessage());
}
catch(IOException iox) {
logger.fatal( + iox.getMessage());
}
}
private Singleton readSingleton() {
Singleton s = null;

try {
FileInputStream fis = new FileInputStream();
ObjectInputStream ois = new ObjectInputStream(fis);
s = (Singleton)ois.readObject();
}
catch(ClassNotFoundException cnf) {
logger.fatal( + cnf.getMessage());
}
catch(NotSerializableException se) {
logger.fatal( + se.getMessage());
}
catch(IOException iox) {
logger.fatal( + iox.getMessage());
}
return s;
}
public void testUnique() {
logger.info();
Singleton another = new Singleton();

logger.info();
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开发者在顾及多线程、类载入器和序列化情况如何实现单例模式。作者也展示了你怎样才能实现一个单例类的注册表,以便能够在运行期指定单例类

单例模式完全剖析(1)---- 探究简单却又使人迷惑的单例模式

概要单例模式是最简单的设计模式之一,但是对于Java的开发者来说,它却有很多缺陷。在本月的专栏中,David Geary探讨了单例模式以及在面对多线程(multithreading)、类装载器(cla...
  • maqj
  • maqj
  • 2003年11月09日 22:07
  • 1169

单例模式完全剖析(2)---- 探究简单却又使人迷惑的单例模式

测试单例模式接下来,我使用与log4j相对应的JUnit来测试单例类,它会贯穿在这篇文章余下的部分。如果你对JUnit或log4j不很熟悉,请参考相关资源。例2是一个用JUnit测试例1的单例模式的案...
  • maqj
  • maqj
  • 2003年11月13日 00:00
  • 977

单例模式完全剖析---- 探究简单却又使人迷惑的单例模式

  单例模式适合于一个类只有一个实例的情况,比如窗口管理器,打印缓冲池和文件系统,它们都是原型的例子。典型的情况是,那些对象的类型被遍及一个软件系统的不同对象访问,因此需要一个全局的访问指针,这便是众...
  • cuisong1984
  • cuisong1984
  • 2008年08月27日 15:14
  • 205

一个单例模式的简单例子

ex1: public class Singleton    {        private static final Singleton singleton = null;           p...
  • silence1214
  • silence1214
  • 2009年03月29日 14:56
  • 23514

PHP设计模式之单例模式简单代码介绍

PHP设计模式之单例模式 单例模式是一种创建型模式,它会限制应用程序,使其只能创建某一特定类类型的一个单一的实例。举例来说,一个web站点将会需要一个数据库连接对象,但是应该有且只能有一个,因此我们...
  • u012675743
  • u012675743
  • 2015年04月09日 18:10
  • 1431

单例模式常用的的两种写法

单例模式很常用,Java中写法各异,哪种更合理高效呢。一般单例都是五种写法。懒汉式,饿汉式,双重校验锁,枚举和静态内部类。什么时候用懒汉式,何时用饿汉式应该清楚。一, 单例模式特点:1)一个类只有一个...
  • dzsw0117
  • dzsw0117
  • 2016年05月07日 09:35
  • 1342

一个简单的单例模式 类的编写

class Singleton     {         private static Singleton instance;         // Constructor     ...
  • yuyanfei125
  • yuyanfei125
  • 2014年05月15日 09:58
  • 715

最简单的单例模式

如果一个类始终只能创建一个实例,则这个类被称为单例类。   在一些特殊的场景下,要求不允许自由创建该类的对象,而是只允许为该类创建一个对象。为了避免其他类自由创建该类的实例,我们把该类的构造器使...
  • u013700340
  • u013700340
  • 2014年04月03日 20:14
  • 1369

单例模式的几种用法比较

1.定义确保某个类只有一个实例,能自行实例化并向整个系统提供这个实例。这里要考虑到多线程和反序列化。2.应用场景 当产生多个对象会消耗过多资源,比如IO和数据操作 某种类型的对象只应该有且只有一个,比...
  • luofen521
  • luofen521
  • 2016年06月30日 10:26
  • 3410

单例模式的几种实现方式

当一个类只能有一个对象时,往往会用到单例模式,例如,现实生活中有很多临界资源,像 打印机、处理器(单核)、皇帝、太子等等,它们都是稀有资源,只能有一个实例对象。下面用java将单例模式实现:     ...
  • z507263441
  • z507263441
  • 2013年11月10日 02:05
  • 10113
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:单例模式完全剖析(3)---- 探究简单却又使人迷惑的单例模式
举报原因:
原因补充:

(最多只允许输入30个字)