分享
 
 
 

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

王朝java/jsp·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

使用注册表

使用一个单例类注册表可以:

在运行期指定单例类

防止产生多个单例类子类的实例

在例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.

}

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("testing singleton serialization...");

writeSingleton();

Singleton s1 = readSingleton();

Singleton s2 = readSingleton();

Assert.assertEquals(true, s1 == s2); }

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

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有