When is a singleton not a singleton?

来源:互联网 发布:mac充电器是什么 编辑:程序博客网 时间:2024/04/30 16:51
 

When is a singleton not a singleton?

Avoid multiple singleton instances by keeping these tips in mind


 

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 (MySingleton.class) {
     _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 in different ObjectOutputStreams, or with calls ObjectOutputStream.reset() between deserializations, 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.

原创粉丝点击