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

使用注册表


使用一个单例类注册表可以:
在运行期指定单例类
防止产生多个单例类子类的实例
在例8的单例类中,保持了一个通过类名进行注册的单例类注册表:
例8带注册表的单例类
  1. importjava.util.HashMap;
  2. importorg.apache.log4j.Logger;
  3. publicclassSingleton{
  4. privatestaticHashMapmap=newHashMap();
  5. privatestaticLoggerlogger=Logger.getRootLogger();
  6. protectedSingleton(){
  7. //Existsonlytothwartinstantiation
  8. }
  9. publicstaticsynchronizedSingletongetInstance(Stringclassname){
  10. if(classname==null)thrownewIllegalArgumentException("Illegalclassname");
  11. Singletonsingleton=(Singleton)map.get(classname);
  12. if(singleton!=null){
  13. logger.info("gotsingletonfrommap:"+singleton);
  14. returnsingleton;
  15. }
  16. if(classname.equals("SingeltonSubclass_One"))
  17. singleton=newSingletonSubclass_One();
  18. elseif(classname.equals("SingeltonSubclass_Two"))
  19. singleton=newSingletonSubclass_Two();
  20. map.put(classname,singleton);
  21. logger.info("createdsingleton:"+singleton);
  22. returnsingleton;
  23. }
  24. //Assumefunctionalityfollowsthat'sattractivetoinherit
  25. }

这段代码的基类首先创建出子类的实例,然后把它们存储在一个Map中。但是基类却得付出很高的代价因为你必须为每一个子类替换它的getInstance()方法。幸运的是我们可以使用反射处理这个问题。

使用反射


在例9的带注册表的单例类中,使用反射来实例化一个特殊的类的对象。与例8相对的是通过这种实现,Singleton.getInstance()方法不需要在每个被实现的子类中重写了。
例9使用反射实例化单例类
  1. importjava.util.HashMap;
  2. importorg.apache.log4j.Logger;
  3. publicclassSingleton{
  4. privatestaticHashMapmap=newHashMap();
  5. privatestaticLoggerlogger=Logger.getRootLogger();
  6. protectedSingleton(){
  7. //Existsonlytothwartinstantiation
  8. }
  9. publicstaticsynchronizedSingletongetInstance(Stringclassname){
  10. Singletonsingleton=(Singleton)map.get(classname);
  11. if(singleton!=null){
  12. logger.info("gotsingletonfrommap:"+singleton);
  13. returnsingleton;
  14. }
  15. try{
  16. singleton=(Singleton)Class.forName(classname).newInstance();
  17. }
  18. catch(ClassNotFoundExceptioncnf){
  19. logger.fatal("Couldn'tfindclass"+classname);
  20. }
  21. catch(InstantiationExceptionie){
  22. logger.fatal("Couldn'tinstantiateanobjectoftype"+classname);
  23. }
  24. catch(IllegalAccessExceptionia){
  25. logger.fatal("Couldn'taccessclass"+classname);
  26. }
  27. map.put(classname,singleton);
  28. logger.info("createdsingleton:"+singleton);
  29. returnsingleton;
  30. }
  31. }


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

封装注册表


例10列出了一个单例注册表类。
例10一个SingletonRegistry类
  1. importjava.util.HashMap;
  2. importorg.apache.log4j.Logger;
  3. publicclassSingletonRegistry{
  4. publicstaticSingletonRegistryREGISTRY=newSingletonRegistry();
  5. privatestaticHashMapmap=newHashMap();
  6. privatestaticLoggerlogger=Logger.getRootLogger();
  7. protectedSingletonRegistry(){
  8. //Existstodefeatinstantiation
  9. }
  10. publicstaticsynchronizedObjectgetInstance(Stringclassname){
  11. Objectsingleton=map.get(classname);
  12. if(singleton!=null){
  13. returnsingleton;
  14. }
  15. try{
  16. singleton=Class.forName(classname).newInstance();
  17. logger.info("createdsingleton:"+singleton);
  18. }
  19. catch(ClassNotFoundExceptioncnf){
  20. logger.fatal("Couldn'tfindclass"+classname);
  21. }
  22. catch(InstantiationExceptionie){
  23. logger.fatal("Couldn'tinstantiateanobjectoftype"+
  24. classname);
  25. }
  26. catch(IllegalAccessExceptionia){
  27. logger.fatal("Couldn'taccessclass"+classname);
  28. }
  29. map.put(classname,singleton);
  30. returnsingleton;
  31. }
  32. }

注意我是把SingletonRegistry类作为一个单例模式实现的。我也通用化了这个注册表以便它能存储和取回任何类型的对象。例11显示了的Singleton类使用了这个注册表。
例11使用了一个封装的注册表的Singleton类
  1. importjava.util.HashMap;
  2. importorg.apache.log4j.Logger;
  3. publicclassSingleton{
  4. protectedSingleton(){
  5. //Existsonlytothwartinstantiation.
  6. }
  7. publicstaticSingletongetInstance(){
  8. return(Singleton)SingletonRegistry.REGISTRY.getInstance(classname);
  9. }
  10. }

上面的Singleton类使用那个注册表的唯一实例通过类名取得单例对象。
现在我们已经知道如何实现线程安全的单例类和如何使用一个注册表去在运行期指定单例类名,接着让我们考查一下如何安排类载入器和处理序列化。

Classloaders


在许多情况下,使用多个类载入器是很普通的--包括servlet容器--所以不管你在实现你的单例类时是多么小心你都最终可以得到多个单例类的实例。如果你想要确保你的单例类只被同一个的类载入器装入,那你就必须自己指定这个类载入器;例如:
  1. privatestaticClassgetClass(Stringclassname)
  2. throwsClassNotFoundException{
  3. ClassLoaderclassLoader=Thread.currentThread().getContextClassLoader();
  4. if(classLoader==null)
  5. classLoader=Singleton.class.getClassLoader();
  6. return(classLoader.loadClass(classname));
  7. }
  8. }

这个方法会尝试把当前的线程与那个类载入器相关联;如果classloader为null,这个方法会使用与装入单例类基类的那个类载入器。这个方法可以用Class.forName()代替。

序列化


如果你序列化一个单例类,然后两次重构它,那么你就会得到那个单例类的两个实例,除非你实现readResolve()方法,像下面这样:
例12一个可序列化的单例类
  1. importorg.apache.log4j.Logger;
  2. publicclassSingletonimplementsjava.io.Serializable{
  3. publicstaticSingletonINSTANCE=newSingleton();
  4. protectedSingleton(){
  5. //Existsonlytothwartinstantiation.
  6. }
  7. [b]privateObjectreadResolve(){
  8. returnINSTANCE;
  9. }[/b]}

上面的单例类实现从readResolve()方法中返回一个唯一的实例;这样无论Singleton类何时被重构,它都只会返回那个相同的单例类实例。
例13测试了例12的单例类:
例13测试一个可序列化的单例类
  1. importjava.io.*;
  2. importorg.apache.log4j.Logger;
  3. importjunit.framework.Assert;
  4. importjunit.framework.TestCase;
  5. publicclassSingletonTestextendsTestCase{
  6. privateSingletonsone=null,stwo=null;
  7. privatestaticLoggerlogger=Logger.getRootLogger();
  8. publicSingletonTest(Stringname){
  9. super(name);
  10. }
  11. publicvoidsetUp(){
  12. sone=Singleton.INSTANCE;
  13. stwo=Singleton.INSTANCE;
  14. }
  15. publicvoidtestSerialize(){
  16. logger.info("testingsingletonserialization...");
  17. [b]writeSingleton();
  18. Singletons1=readSingleton();
  19. Singletons2=readSingleton();
  20. Assert.assertEquals(true,s1==s2);[/b]}
  21. privatevoidwriteSingleton(){
  22. try{
  23. FileOutputStreamfos=newFileOutputStream("serializedSingleton");
  24. ObjectOutputStreamoos=newObjectOutputStream(fos);
  25. Singletons=Singleton.INSTANCE;
  26. oos.writeObject(Singleton.INSTANCE);
  27. oos.flush();
  28. }
  29. catch(NotSerializableExceptionse){
  30. logger.fatal("NotSerializableException:"+se.getMessage());
  31. }
  32. catch(IOExceptioniox){
  33. logger.fatal("IOException:"+iox.getMessage());
  34. }
  35. }
  36. privateSingletonreadSingleton(){
  37. Singletons=null;
  38. try{
  39. FileInputStreamfis=newFileInputStream("serializedSingleton");
  40. ObjectInputStreamois=newObjectInputStream(fis);
  41. s=(Singleton)ois.readObject();
  42. }
  43. catch(ClassNotFoundExceptioncnf){
  44. logger.fatal("ClassNotFoundException:"+cnf.getMessage());
  45. }
  46. catch(NotSerializableExceptionse){
  47. logger.fatal("NotSerializableException:"+se.getMessage());
  48. }
  49. catch(IOExceptioniox){
  50. logger.fatal("IOException:"+iox.getMessage());
  51. }
  52. returns;
  53. }
  54. publicvoidtestUnique(){
  55. logger.info("testingsingletonuniqueness...");
  56. Singletonanother=newSingleton();
  57. logger.info("checkingsingletonsforequality");
  58. Assert.assertEquals(true,sone==stwo);
  59. }
  60. }

前面这个测试案例序列化例12中的单例类,并且两次重构它。然后这个测试案例检查看是否被重构的单例类实例是同一个对象。下面是测试案例的输出:
  1. Buildfile:build.xml
  2. init:
  3. [echo]Build20030422(22-04-200311:32)
  4. compile:
  5. run-test-text:
  6. [java].INFOmain:testingsingletonserialization...
  7. [java].INFOmain:testingsingletonuniqueness...
  8. [java]INFOmain:checkingsingletonsforequality
  9. [java]Time:0.1
  10. [java]OK(2tests)

单例模式结束语


单例模式简单却容易让人迷惑,特别是对于Java的开发者来说。在这篇文章中,作者演示了Java开发者在顾及多线程、类载入器和序列化情况如何实现单例模式。作者也展示了你怎样才能实现一个单例类的注册表,以便能够在运行期指定单例类。
阅读更多

没有更多推荐了,返回首页