彩虹天堂
技术源于生活
posts - 0,  comments - 2,  trackbacks - 0
一种性能改进的方法

寻找一种性能改进方法时,你可能会选择像下面这样重写getInstance()方法:
Java代码 复制代码
  1. public static Singleton getInstance() {    
  2.    if(singleton == null) {    
  3.       synchronized(Singleton.class) {     
  4.          singleton = new Singleton();    
  5.       }    
  6.    }    
  7.    return singleton;    
  8. }   


这个代码片段只同步了关键的代码,而不是同步整个方法。然而这段代码却不是线程安全的。考虑一下下面的假定:线程1进入同步块,并且在它给singleton成员变量赋值之前线程1被切换。接着另一个线程进入if块。第二个线程将等待直到第一个线程完成,并且仍然会得到两个不同的单例类实例。有修复这个问题的方法吗?请读下去。

双重加锁检查

初看上去,双重加锁检查似乎是一种使懒汉式实例化为线程安全的技术。下面的代码片段展示了这种技术:
Java代码 复制代码
  1. public static Singleton getInstance() {    
  2.   if(singleton == null) {    
  3.      synchronized(Singleton.class) {    
  4.        if(singleton == null) {    
  5.          singleton = new Singleton();    
  6.        }    
  7.     }    
  8.   }    
  9.   return singleton;    
  10. }   


如果两个线程同时访问getInstance()方法会发生什么?想像一下线程1进行同步块马上又被切换。接着,第二个线程进入if 块。当线程1退出同步块时,线程2会重新检查看是否singleton实例仍然为null。因为线程1设置了singleton成员变量,所以线程2的第二次检查会失败,第二个单例类实例也就不会被创建。似乎就是如此。
不幸的是,双重加锁检查不会保证正常工作,因为编译器会在Singleton的构造方法被调用之前随意给singleton赋一个值。如果在singleton引用被赋值之后而被初始化之前线程1被切换,线程2就会被返回一个对未初始化的单例类实例的引用。

一个改进的线程安全的单例模式实现

例7列出了一个简单、快速而又是线程安全的单例模式实现:
例7.一个简单的单例类
Java代码 复制代码
  1. public class Singleton {    
  2.    public final static Singleton INSTANCE = new Singleton();    
  3.    private Singleton() {    
  4.          // Exists only to defeat instantiation.    
  5.       }    
  6. }   


这段代码是线程安全的是因为静态成员变量一定会在类被第一次访问时被创建。你得到了一个自动使用了懒汉式实例化的线程安全的实现;你应该这样使用它:
Java代码 复制代码
  1. Singleton singleton = Singleton.INSTANCE;    
  2. singleton.dothis();    
  3. singleton.dothat();    
  4. ...   


当然万事并不完美,前面的Singleton只是一个折衷的方案;如果你使用那个实现,你就无法改变它以便后来你可能想要允许多个单例类的实例。用一种更折哀的单例模式实现(通过一个getInstance()方法获得实例)你可以改变这个方法以便返回一个唯一的实例或者是数百个实例中的一个.你不能用一个公开且是静态的(public static)成员变量这样做.

你可以安全的使用例7的单例模式实现或者是例1的带一个同步的getInstance()方法的实现.然而,我们必须要研究另一个问题:你必须在编译期指定这个单例类,这样就不是很灵活.一个单例类的注册表会让我们在运行期指定一个单例类.

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

在运行期指定单例类

防止产生多个单例类子类的实例
在例8的单例类中,保持了一个通过类名进行注册的单例类注册表:
例8 带注册表的单例类

Java代码 复制代码
  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 使用反射实例化单例类
Java代码 复制代码
  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类

Java代码 复制代码
  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类

Java代码 复制代码
  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类使用那个注册表的唯一实例通过类名取得单例对象。
现在我们已经知道如何实现线程安全的单例类和如何使用一个注册表去在运行期指定单例类名,接着让我们考查一下如何安排类载入器和处理序列化。
posted on 2008-05-05 22:11 bcterry 阅读(168) 评论(0)  编辑  收藏

只有注册用户登录后才能发表评论。


网站导航:
 

<2024年12月>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

留言簿

文章档案

搜索

  •  

最新评论