发信人: ifeelyou(非鱼) 
整理人: telescope(2001-02-28 09:08:40), 站内信件
 | 
 
 
发言者:hdp     主  页:Java联盟   
 时  间:2001/01/15 11:06:34    来  自:www.javaunion.org 
 
 
 --------------------------------------------------------------------------------
 
 ----何时单件(Singleton)不再是单件?
 
 ----作者 Joshua Fox 
 摘要
 
 在单件设计模式中,单件类从来都只会有一个该类的实例。然而,有时你会发现存在不止一个单件的实例存在
 
 。出现这些意外的情况情况的可能是特定的类加载器(class loaders)、多线程(MT)、分布计算系统(
 
 Distributed System)和对象序列化(object serialization)。Joshua在本文中将就如何避免和处理这种多单
 
 件情况做详细阐述。
 
 
 单件设计模(The Singleton Pattern)——保证一个类仅有一个实例,并提供一个全局访问他的访问点。在很
 
 多时候都是有用的,但是当单件类出现同时几个实例并存时就出错了。这种情况是如何出现的呢?如何避免呢
 
 ?
 
 单件(Singleton)的目的是控制对象的创建,在整个规定使用范围内单件类只能有一个实例,这和静态域有些
 
 类似。单件通常用来控制对资源的访问,如数据库连接缓冲池(dbpool)访问、网络连接套接字(socket pool)
 
 使用等。例如,如果你的jdbc驱动的连接授权(license)只有5个(这种情况其实经常有),你用一个单件类来
 
 实现数据库连接缓冲池,那么在多线程系统中应该统一使用该单件类的一个实例并且由该实例对数据库连接进
 
 行控制管理。
 
 单件类可以是具有状态性的,并且在整个范围内其状态是唯一的。如上面的例子中5个连接的状态可能是闲置可
 
 用、部分闲置可用等等状态。
 
 另一方面,单件也可以是无状态的,如提供一种不需要任何参数信息的工具方法的类。在这种情况下,因无需
 
 实例化多个对象因此单件是适合用来设计这种工具类的。
 
 不要把单件单纯的看作是实现一个全局变量,从工厂设计模式这种观点出发,单件使你通过确定创建对象的先决条件和实际需求压缩和控制创建过程。
 
 然而,在特定环境下两个或以上的单件可能变成神秘莫测的东东,象分布计算必须保证单件所提供的唯一性。例如,如果你的单件的含义是全局唯一的用户界面窗口而在应用中出现两个单件的实例,将有两个你的应用的窗口出现在用户的屏幕上——这将使用户很糊涂。另一个例子是同时创建了两个单件类的计数器的实例可能导致的混乱情况……
 
 本文将描述这些现象并提出如何去避免,在讨论如何实现单件之后,我将给出一些实例来说明多个单件实例的情况是如何产生的、如何去避免!
 
 
 实现单件(Implementing Singletons )
 
 
 There are a few ways to implement Singletons. Although you can get Singleton-like behavior with 
 
 static fields and methods [for example, java.lang.Math.sin(double)], you gain more flexibility by 
 
 creating an instance. With Singletons implemented as single instances instead of static class 
 
 members, you can initialize the Singleton lazily, creating it only when it is first used. 
 
 Likewise, with a Singleton implemented as single instance, you leave open the possibility of 
 
 altering the class to create more instances in the future. With some implementations of the 
 
 Singleton, you allow writers of subclasses to override methods polymorphically, something not 
 
 possible with static methods. 
 
 Most commonly, you implement a Singleton in Java by having a single instance of the class as a 
 
 static field. You can create that instance at class-loading time by assigning a newly created 
 
 object to the static field in the field declaration, as seen in Listing 1. 
 
 Listing 1
 public class MySingleton { 
 private static MySingleton _instance = 
  new MySingleton(); 
 
 private MySingleton() { 
  // construct object . . . 
 } 
 
 public static synchronized MySingleton getInstance() { 
  return _instance; 
 } 
 
 // Remainder of class definition . . . 
 
 
 Alternatively, you can instantiate it lazily, on first demand, as seen in Listing 2. You keep the 
 
 constructor private to prevent instantiation by outside callers. 
 
 Listing 2
 public class MySingleton { 
 private static MySingleton _instance; 
 
 private MySingleton() { 
  // construct object . . . 
 } 
 
 // For lazy initialization 
 public static synchronized MySingleton getInstance() { 
  if (_instance==null) { 
   _instance = new MySingleton(); 
  } 
  return _instance; 
 } 
  // Remainder of class definition . . . 
 } 
 
 
 Both Singleton implementations do not allow convenient subclassing, since getInstance(), being 
 
 static, cannot be overridden polymorphically. Other implementations prove more flexible. While I 
 
 don't have the space to describe alternative implementations in detail, here are a couple of 
 
 possibilities: 
 
 
 If you implement a factory class with a method that returns the Singleton instance, you allow 
 
 yourself flexibility in the runtime class of the return value, which can be either MySingleton or 
 
 a subclass thereof. You may have to make the constructor nonprivate to make that work. 
 You can have a SingletonFactory class with a globally accessible map of class names or Class 
 
 objects to Singleton instances. Again, the runtime type of the instances can be either the type 
 
 indicated by the class name or a subclass, and the constructor will not be private. 
 For other possible implementations, see "Implementing the Singleton Pattern in Java" in 
 
 Resources. As I discuss the Singleton, keep the above implementations in mind, although many of 
 
 those phenomena can occur for any implementation of the Singleton. 
 
 Now that we've looked briefly at implementation, let's turn to the heart of this article: how two 
 
 Singletons can exist simultaneously. 
 
 
 
 在多个虚拟机中的多单件(Multiple Singletons in two or more virtual machines )
 
 When copies of the Singleton class run in multiple VMs, an instance is created for each machine. 
 
 That each VM can hold its own Singleton might seem obvious but, in distributed systems such as 
 
 those using EJBs, Jini, and RMI, it's not so simple. Since intermediate layers can hide the 
 
 distributed technologies, to tell where an object is really instantiated may be difficult. 
 
 For example, only the EJB container decides how and when to create EJB objects or to recycle 
 
 existing ones. The EJB may exist in a different VM from the code that calls it. Moreover, a 
 
 single EJB can be instantiated simultaneously in several VMs. For a stateless session bean, 
 
 multiple calls to what appears, to your code, to be one instance could actually be calls to 
 
 different instances on different VMs. Even an entity EJB can be saved through a persistence 
 
 mechanism between calls, so that you have no idea what instance answers your method calls. (The 
 
 primary key that is part of the entity bean spec is needed precisely because referential identity 
 
 is of no use in identifying the bean.) 
 
 The EJB containers' ability to spread the identity of a single EJB instance across multiple VMs 
 
 causes confusion if you try to write a Singleton in the context of an EJB. The instance fields of 
 
 the Singleton will not be globally unique. Because several VMs are involved for what appears to 
 
 be the same object, several Singleton objects might be brought into existence. 
 
 Systems based on distributed technologies such as EJB, RMI, and Jini should avoid Singletons that 
 
 hold state. Singletons that do not hold state but simply control access to resources are also not 
 
 appropriate for EJBs, since resource management is the role of the EJB container. However, in 
 
 other distributed systems, Singleton objects that control resources may be used on the 
 
 understanding that they are not unique in the distributed system, just in the particular VM. 
 
 Multiple Singletons simultaneously loaded by different class loaders 
 When two class loaders load a class, you actually have two copies of the class, and each one can 
 
 have its own Singleton instance. That is particularly relevant in servlets running in certain 
 
 servlet engines (iPlanet for example), where each servlet by default uses its own class loader. 
 
 Two different servlets accessing a joint Singleton will, in fact, get two different objects. 
 
 Multiple class loaders occur more commonly than you might think. When browsers load classes from 
 
 the network for use by applets, they use a separate class loader for each server address. 
 
 Similarly, Jini and RMI systems may use a separate class loader for the different code bases from 
 
 which they download class files. If your own system uses custom class loaders, all the same 
 
 issues may arise. 
 
 If loaded by different class loaders, two classes with the same name, even the same package name, 
 
 are treated as distinct -- even if, in fact, they are byte-for-byte the same class. The different 
 
 class loaders represent different namespaces that distinguish classes (even though the classes' 
 
 names are the same), so that the two MySingleton classes are in fact distinct. (See "Class 
 
 Loaders as a Namespace Mechanism" in Resources.) Since two Singleton objects belong to two 
 
 classes of the same name, it will appear at first glance that there are two Singleton objects of 
 
 the same class. 
 
 Singleton classes destroyed by garbage collection, then reloaded 
 When a Singleton class is garbage-collected and then reloaded, a new Singleton instance is 
 
 created. Any class can be garbage-collected when no other object holds reference to the class or 
 
 its instances. If no object holds a reference to the Singleton object, then the Singleton class 
 
 may disappear, later to be reloaded when the Singleton is again needed. In that case, a new 
 
 Singleton object will be created. Any static or instance fields saved for the object will be lost 
 
 and reinitialized. 
 
 JDK 1.2 VMs are less problematic in that regard than JDK 1.1 (see Programming Java threads in the 
 
 real world, Part 7" in Resources), but class garbage collection can still occur. You can avoid 
 
 that by holding a reference to the Singleton class or object in some other object that persists 
 
 for the program's life. You can also set your VM to have no class garbage collection (-Xnoclassgc 
 
 on the JRE 1.3, or -noclassgc on the IBM JVM). Keep in mind that if you have a long-running 
 
 program that frequently reloads classes (perhaps through special class loaders such as the remote 
 
 class loaders), you have to consider whether that could cause a problematic buildup of garbage 
 
 classes in the VM. 
 
 Purposely reloaded Singleton classes 
 Classes are reloaded not only after class garbage-collection; they can also be reloaded at Java 
 
 programs' request. The servlet specifications allow servlet engines to do that at any time. When 
 
 the servlet engine decides to unload a servlet class, it calls destroy(), then discards the 
 
 servlet class; later, the servlet engine can reload the servlet class, instantiate the servlet 
 
 object, and initialize it by calling init(). In practice, the process of unloading and reloading 
 
 may occur in a servlet engine when a servlet class or JSP changes. 
 
 Like the previous two cases, the present case involves newly loaded classes. Here, however, 
 
 classes are ditched on purpose, while a new copy of the class loads. 
 
 Depending on the servlet engine, when an old servlet class is discarded, the associated classes 
 
 might not be, even if they have changed. So if a servlet gets a reference to a Singleton object, 
 
 you may find that there is one Singleton object associated with the old servlet class and one 
 
 associated with the new. 
 
 As servlet engines differ in their class-reloading policies, the Singleton behavior is 
 
 unpredictable unless you understand how your servlet engine's class-loading mechanisms work. 
 
 Similar problems can occur if you hold a reference from another object to a servlet and some 
 
 chain of references keeps that object from the garbage collector. Then, when the servlet class 
 
 should be discarded, it cannot be, and you may find the servlet class loaded twice in the VM. 
 
 Multiple instances resulting from incorrect synchronization 
 One of the common Singleton implementations uses lazy initialization of the one instance. That 
 
 means that the instance is not created when the class loads, but rather when it is first used. 
 
 (See Listing 2.) A common mistake with that implementation is to neglect synchronization, which 
 
 can lead to multiple instances of the singleton class. (See Listing 3.) 
 
 Listing 3
 // error, no synchronization on method 
 public static MySingleton getInstance() { 
 if (_instance==null) { 
  _instance = new MySingleton(); 
 } 
 
 return _instance; 
 } 
 
 
 Two Singletons will be created if the constructor runs and simultaneously another thread calls 
 
 the method. Thread-safe code is particularly important in Singletons, since that Design Pattern 
 
 is meant to give the user a single point of access that hides the complexities of the 
 
 implementation, including multithreading issues. 
 
 Multiple instances can be created even if you add a synchronized(this) block to the constructor 
 
 call, as in Listing 4: 
 
 Listing 4
 // Also an error, synchronization does not prevent 
 // two calls of constructor. 
 public static MySingleton getInstance() { 
 if (_instance==null) { 
   synchronized (this) { 
    _instance = new MySingleton(); 
   } 
 } 
 return _instance; 
 } 
 
 
 In the correct solution, seen in Listing 5, make getInstance() a synchronized method: 
 
 Listing 5
 // correct solution 
 public static synchronized MySingleton getInstance() { 
 // . . . continue as in Listing 3 
 
 
 Double-checked locking is another common solution but, unfortunately, it does not work (see 
 
 Listing 6). 
 
 Listing 6
 // Double-checked locking -- don't use 
 public static MySingleton getInstance() { 
 if (_instance==null) { 
   synchronized (MySingleton.class) { 
    if (_instance==null) { 
     _instance = new MySingleton(); 
    } 
   } 
 } 
 } 
 
 
 In this situation, we intend to avoid the expense of grabbing the lock of the Singleton class 
 
 every time the method is called. The lock is grabbed only if the Singleton instance does not 
 
 exist, and then the existence of the instance is checked again in case another thread passed the 
 
 first check an instant before the current thread. 
 
 Unfortunately, double-checked locking causes problems. To wit, compiler optimizations can make 
 
 the assignment of the new Singleton object before all its fields are initialized. (See 
 
 "Double-checked locking is broken" in Resources.) The only practical solution is to synchronize 
 
 the getInstance() method (as in Listing 2). 
 
 Multiple Singletons arising when someone has subclassed your Singleton 
 The Singleton Design Pattern is meant to give you control over access to the Singleton class. 
 
 While I have mostly discussed the control of instantiation, other code can access your class 
 
 another way: by subclassing it. 
 
 The uniqueness of the class cannot be imposed as a compile-time constraint on the subclass unless 
 
 you use a private constructor. If you want to allow subclassing, for example, you might make the 
 
 constructor protected. A subclass could then expose a public constructor, allowing anyone to make 
 
 instances. Since an instance of a subclass is an instance of your superclass, you could find 
 
 multiple instances of the Singleton. 
 
 Multiple Singletons created by a factory specially asked to create multiple objects 
 One of the strengths of the Singleton design pattern, as opposed to static methods, is that if 
 
 you change your mind and want more than one, the Singleton class can be easily altered. 
 
 For example, most servlets run as Singletons in their servlet engines. Since that can cause 
 
 threading problems, in one alternative (not recommended, but available) the servlet can implement 
 
 SingleThreadModel. In that case, the servlet engine may, if necessary, create more than one 
 
 servlet instance. If you are used to the more common Singleton servlets, you may forget that some 
 
 servlets can occur in multiple instances. 
 
 Copies of a Singleton object that has undergone serialization and deserialization 
 If you have a serialized object and deserialize it twice, you get two distinct objects, not two 
 
 references to the same object. 
 
 Likewise, when you serialize an object with an ObjectOutputStream, the closure of its graph of 
 
 references serializes with it. If the ObjectOutputStream closes and a new one opens, or if 
 
 reset() is called on the ObjectOutputStream, the graph of references breaks and a new one is 
 
 started. So if you serialize one Singleton twice, the two serialized objects take on separate 
 
 identities. 
 
 The object serialization of the java.io package is not the only way to serialize Java objects. 
 
 New mechanisms of object serialization with XML have been developed, including those associated 
 
 with SOAP, WDDX, and KOALA, among others. With all those mechanisms, a reconstituted object loses 
 
 its referential identity, and you have to consider carefully whether your Singleton is still a 
 
 Singleton. 
 
 Multiple Singletons caused by problems in a factory 
 In some implementations of creational design patterns in the factory family, the factory is a 
 
 Singleton structured to create an instance of another class. That second class might not have any 
 
 Singleton-like protection against multiple instantiation, on the grounds that the factory will 
 
 ensure that it constructs only one instance. 
 
 If you accidentally have more than one factory object for one of the reasons above, you will 
 
 still have two of the created objects, even if each factory object is built correctly. 
 
 Conclusion 
 Singletons are a useful way to control access to a class, making sure that only one instance can 
 
 exist. In some none-too-uncommon circumstances, however, multiple instances can occur, even in a 
 
 class coded as a Singleton. By being aware of the possibility, you can be sure that your 
 
 Singleton really is a Singleton. 
 
 In this article, I've presented some ways that the multiple Singleton might emerge. If you've 
 
 discovered any others, I'd be interested in hearing about them. 
 
 Acknowledgement 
 My thanks to Alexander Radzin for valuable comments on this article. 
 
 About the author 
 Joshua Fox is senior architect at Surf&Call Network Services at which he develops distributed 
 
 Java systems. His current project is an Internet service that allows customers and call-center 
 
 agents to surf the Web together. You can view his software engineering Website at 
 
 http://www.joshuafox.com. 
 
 译注:
 呵呵,因时间关系仅为大家做个开篇乱译了。如果想更好掌握设计模式,建议还是看看各种英文书和文章,毕竟国内的实际情况还是比人家落后很多。不过没关系,大家都用点心来学习、来普及、来使用,我想不论是CMM、PSP、TSP、软件工程还是设计模式肯定会在在国内年轻一代程序员中会有大的效果。欢迎更多的朋友翻译、转贴更多的相关内容!也诚恳欢迎同行提出建议、批评和帮助以便我们做得更好……
 
 
                           胡德平 2001.01.15 北京清华园
 
 
 
 
 
 
 
 
 
 
 
 
 勇者无畏,强者无敌!
 突破极限,贴近真理。
  
 
 【帖子管理】   用BBS风格观看   << 返回   返回论坛首页 
 
 
 --------------------------------------------------------------------------------
 
 本帖版权归原作者,其它网站转载须注明出处,传统媒体转载须事先与原作者和中国Java阵线联盟论坛联系。
 
 
 --------------------------------------------------------------------------------
 
 
 回复以上发言
 
      
 您的姓名    密  码       删除你的cookie记录
 发言主题  
 
  随便说说。  新发现哦!  特大新闻!  为什么呢? 
  还有更多。  大家小心!  我来回答。  原来如此! 
  非常同意!  哇!厉害。  胡说八道!  我不同意。 
  心情好坏。  气死我了!  嘻,嘻嘻!  嗯,很好! 
 
 文章内容 
 
  
 请选择   加入我的签名    显示我的邮箱地址 
 
 请查看被禁止使用的HTML标签。 本帖是否支持HTML-->是  否     
 
  | 
 
 
 |