UML软件工程组织

单例模式完全剖析(3)---- 探究简单却又使人迷惑的单例模式
kevin 翻译 选自:java研究组织
使用注册表

使用一个单例类注册表可以:
  • 在运行期指定单例类
  • 防止产生多个单例类子类的实例
    在例8的单例类中,保持了一个通过类名进行注册的单例类注册表:
    例8 带注册表的单例类
    1. import java.util.HashMap;
    2. import org.apache.log4j.Logger;
    3.  
    4. public class Singleton {
    5.    private static HashMap map = new HashMap();
    6.    private static Logger logger = Logger.getRootLogger();
    7.  
    8.    protected Singleton() {
    9.       // Exists only to thwart instantiation
    10.    }
    11.    public static synchronized Singleton getInstance(String classname) {
    12.       if(classname == nullthrow new IllegalArgumentException("Illegal classname");
    13.          Singleton singleton = (Singleton)map.get(classname);
    14.  
    15.       if(singleton != null) {
    16.          logger.info("got singleton from map: " + singleton);
    17.          return singleton;
    18.       }
    19.       if(classname.equals("SingeltonSubclass_One"))
    20.             singleton = new SingletonSubclass_One();         
    21.          else if(classname.equals("SingeltonSubclass_Two"))
    22.             singleton = new SingletonSubclass_Two();
    23.  
    24.       map.put(classname, singleton);
    25.       logger.info("created singleton: " + singleton);
    26.       return singleton;
    27.    }
    28.    // Assume functionality follows that's attractive to inherit
    29. }

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

    使用反射


    在例9的带注册表的单例类中,使用反射来实例化一个特殊的类的对象。与例8相对的是通过这种实现,Singleton.getInstance()方法不需要在每个被实现的子类中重写了。
    例9 使用反射实例化单例类
    1. import java.util.HashMap;
    2. import org.apache.log4j.Logger;
    3.  
    4. public class Singleton {
    5.    private static HashMap map = new HashMap();
    6.    private static Logger logger = Logger.getRootLogger();
    7.  
    8.    protected Singleton() {
    9.       // Exists only to thwart instantiation
    10.    }
    11.    public static synchronized Singleton getInstance(String classname) {
    12.       Singleton singleton = (Singleton)map.get(classname);
    13.  
    14.       if(singleton != null) {
    15.          logger.info("got singleton from map: " + singleton);
    16.          return singleton;
    17.       }
    18.       try {
    19.          singleton = (Singleton)Class.forName(classname).newInstance();
    20.       }
    21.       catch(ClassNotFoundException cnf) {
    22.          logger.fatal("Couldn't find class " + classname);    
    23.       }
    24.       catch(InstantiationException ie) {
    25.          logger.fatal("Couldn't instantiate an object of type " + classname);    
    26.       }
    27.       catch(IllegalAccessException ia) {
    28.          logger.fatal("Couldn't access class " + classname);    
    29.       }
    30.       map.put(classname, singleton);
    31.       logger.info("created singleton: " + singleton);
    32.  
    33.       return singleton;
    34.    }
    35. }


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

    封装注册表


    例10列出了一个单例注册表类。
    例10 一个SingletonRegistry类
    1. import java.util.HashMap;
    2. import org.apache.log4j.Logger;
    3.  
    4. public class SingletonRegistry {
    5.    public static SingletonRegistry REGISTRY = new SingletonRegistry();
    6.  
    7.    private static HashMap map = new HashMap();
    8.    private static Logger logger = Logger.getRootLogger();
    9.  
    10.    protected SingletonRegistry() {
    11.       // Exists to defeat instantiation
    12.    }
    13.    public static synchronized Object getInstance(String classname) {
    14.       Object singleton = map.get(classname);
    15.  
    16.       if(singleton != null) {
    17.          return singleton;
    18.       }
    19.       try {
    20.          singleton = Class.forName(classname).newInstance();
    21.          logger.info("created singleton: " + singleton);
    22.       }
    23.       catch(ClassNotFoundException cnf) {
    24.          logger.fatal("Couldn't find class " + classname);    
    25.       }
    26.       catch(InstantiationException ie) {
    27.          logger.fatal("Couldn't instantiate an object of type " + 
    28.                        classname);    
    29.       }
    30.       catch(IllegalAccessException ia) {
    31.          logger.fatal("Couldn't access class " + classname);    
    32.       }
    33.       map.put(classname, singleton);
    34.       return singleton;
    35.    }
    36. }

    注意我是把SingletonRegistry类作为一个单例模式实现的。我也通用化了这个注册表以便它能存储和取回任何类型的对象。例11显示了的Singleton类使用了这个注册表。
    例11 使用了一个封装的注册表的Singleton类
    1. import java.util.HashMap;
    2. import org.apache.log4j.Logger;
    3.  
    4. public class Singleton {
    5.  
    6.    protected Singleton() {
    7.       // Exists only to thwart instantiation.
    8.    }
    9.    public static Singleton getInstance() {
    10.       return (Singleton)SingletonRegistry.REGISTRY.getInstance(classname);
    11.    }
    12. }

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

    Classloaders


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

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

    序列化


    如果你序列化一个单例类,然后两次重构它,那么你就会得到那个单例类的两个实例,除非你实现readResolve()方法,像下面这样:
    例12 一个可序列化的单例类
    1. import org.apache.log4j.Logger;
    2.  
    3. public class Singleton implements java.io.Serializable {
    4.    public static Singleton INSTANCE = new Singleton();
    5.  
    6.    protected Singleton() {
    7.       // Exists only to thwart instantiation.
    8.    }
    9. [b]      private Object readResolve() {
    10.             return INSTANCE;
    11.       }[/b]}

    上面的单例类实现从readResolve()方法中返回一个唯一的实例;这样无论Singleton类何时被重构,它都只会返回那个相同的单例类实例。
    例13测试了例12的单例类:
    例13 测试一个可序列化的单例类
    1. import java.io.*;
    2. import org.apache.log4j.Logger;
    3. import junit.framework.Assert;
    4. import junit.framework.TestCase;
    5.  
    6. public class SingletonTest extends TestCase {
    7.    private Singleton sone = null, stwo = null;
    8.    private static Logger logger = Logger.getRootLogger();
    9.  
    10.    public SingletonTest(String name) {
    11.       super(name);
    12.    }
    13.    public void setUp() {
    14.       sone = Singleton.INSTANCE;
    15.       stwo = Singleton.INSTANCE;
    16.    }
    17.    public void testSerialize() {
    18.       logger.info("testing singleton serialization...");
    19. [b]      writeSingleton();
    20.       Singleton s1 = readSingleton();
    21.       Singleton s2 = readSingleton();
    22.       Assert.assertEquals(true, s1 == s2);[/b]   }
    23.    private void writeSingleton() {
    24.       try {
    25.          FileOutputStream fos = new FileOutputStream("serializedSingleton");
    26.          ObjectOutputStream oos = new ObjectOutputStream(fos);
    27.          Singleton s = Singleton.INSTANCE;
    28.  
    29.          oos.writeObject(Singleton.INSTANCE);
    30.          oos.flush();
    31.       }
    32.       catch(NotSerializableException se) {
    33.          logger.fatal("Not Serializable Exception: " + se.getMessage());
    34.       }
    35.       catch(IOException iox) {
    36.          logger.fatal("IO Exception: " + iox.getMessage());
    37.       }
    38.    }
    39.    private Singleton readSingleton() {
    40.       Singleton s = null;
    41.  
    42.       try {
    43.          FileInputStream fis = new FileInputStream("serializedSingleton");
    44.          ObjectInputStream ois = new ObjectInputStream(fis);
    45.          s = (Singleton)ois.readObject();
    46.       }
    47.       catch(ClassNotFoundException cnf) {
    48.          logger.fatal("Class Not Found Exception: " + cnf.getMessage());
    49.       }
    50.       catch(NotSerializableException se) {
    51.          logger.fatal("Not Serializable Exception: " + se.getMessage());
    52.       }
    53.       catch(IOException iox) {
    54.          logger.fatal("IO Exception: " + iox.getMessage());
    55.       }
    56.       return s;
    57.    }
    58.    public void testUnique() {
    59.       logger.info("testing singleton uniqueness...");
    60.       Singleton another = new Singleton();
    61.  
    62.       logger.info("checking singletons for equality");
    63.       Assert.assertEquals(true, sone == stwo);
    64.    }
    65. }

    前面这个测试案例序列化例12中的单例类,并且两次重构它。然后这个测试案例检查看是否被重构的单例类实例是同一个对象。下面是测试案例的输出:
    1.  
    2. Buildfile: build.xml
    3.  
    4. init:
    5.      [echo] Build 20030422 (22-04-2003 11:32)
    6.  
    7. compile:
    8.  
    9. run-test-text:
    10.      [java] .INFO main: testing singleton serialization...
    11.      [java] .INFO main: testing singleton uniqueness...
    12.      [java] INFO main: checking singletons for equality
    13.  
    14.      [java] Time: 0.1
    15.  
    16.      [java] OK (2 tests)

    单例模式结束语


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

 

版权所有:UML软件工程组织