how to Simply Singleton Navigate the deceptively simple Singleton pattern---reference

By David Geary

The Singleton pattern is deceptively simple, even and especially for Java developers. In this classic JavaWorld article, David Geary demonstrates how Java developers implement singletons, with code examples for multithreading, classloaders, and serialization using the Singleton pattern. He concludes with a look at implementing singleton registries in order to specify singletons at runtime.

Sometimes it's appropriate to have exactly one instance of a class: window managers, print spoolers, and filesystems are prototypical examples. Typically, those types of objects—known as singletons—are accessed by disparate objects throughout a software system, and therefore require a global point of access. Of course, just when you're certain you will never need more than one instance, it's a good bet you'll change your mind.

The Singleton design pattern addresses all of these concerns. With the Singleton design pattern you can:

  • Ensure that only one instance of a class is created
  • Provide a global point of access to the object
  • Allow multiple instances in the future without affecting a singleton class's clients

Although the Singleton design pattern—as evidenced below by the figure below—is one of the simplest design patterns, it presents a number of pitfalls for the unwary Java developer. This article discusses the Singleton design pattern and addresses those pitfalls.

More about Java design patterns

You can read all of David Geary's Java Design Patterns columns, or view a listing of JavaWorld's most recent articles about Java design patterns. See "Design patterns, the big picture" for a discussion about the pros and cons of using the Gang of Four patterns. Want more? Get the Enterprise Java newsletter delivered to your inbox.

The Singleton pattern

In Design Patterns: Elements of Reusable Object-Oriented Software, the Gang of Four describe the Singleton pattern like this:

Ensure a class has only one instance, and provide a global point of access to it.

The figure below illustrates the Singleton design pattern class diagram.

Singleton class diagram

As you can see, there's not a whole lot to the Singleton design pattern. Singletons maintain a static reference to the sole singleton instance and return a reference to that instance from a static instance()method.

Example 1 shows a classic Singleton design pattern implementation:

Example 1. The classic singleton

public class ClassicSingleton {
   private static ClassicSingleton instance = null;
   protected ClassicSingleton() {
      // Exists only to defeat instantiation.
   }
   public static ClassicSingleton getInstance() {
      if(instance == null) {
         instance = new ClassicSingleton();
      }
      return instance;
   }
}

The singleton implemented in Example 1 is easy to understand. The ClassicSingleton class maintains a static reference to the lone singleton instance and returns that reference from the static getInstance() method.

There are several interesting points concerning theClassicSingleton class. First, ClassicSingleton employs a technique known as lazy instantiation to create the singleton; as a result, the singleton instance is not created until the getInstance() method is called for the first time. This technique ensures that singleton instances are created only when needed.

Second, notice that ClassicSingleton implements a protected constructor so clients cannot instantiate ClassicSingleton instances; however, you may be surprised to discover that the following code is perfectly legal:

public class SingletonInstantiator { 
  public SingletonInstantiator() { 
   ClassicSingleton instance = ClassicSingleton.getInstance();
ClassicSingleton anotherInstance =
new ClassicSingleton();
       ... 
  } 
}

How can the class in the preceding code fragment—which does not extendClassicSingleton—create a ClassicSingleton instance if the ClassicSingleton constructor is protected? The answer is that protected constructors can be called by subclasses and by other classes in the same package. Because ClassicSingleton andSingletonInstantiator are in the same package (the default package),SingletonInstantiator() methods can create ClassicSingleton instances. This dilemma has two solutions: You can make the ClassicSingleton constructor private so that onlyClassicSingleton() methods call it; however, that means ClassicSingleton cannot be subclassed. Sometimes, that is a desirable solution; if so, it's a good idea to declare your singleton class final, which makes that intention explicit and allows the compiler to apply performance optimizations. The other solution is to put your singleton class in an explicit package, so classes in other packages (including the default package) cannot instantiate singleton instances.

A third interesting point about ClassicSingleton: it's possible to have multiple singleton instances if classes loaded by different classloaders access a singleton. That scenario is not so far-fetched; for example, some servlet containers use distinct classloaders for each servlet, so if two servlets access a singleton, they will each have their own instance.

Fourth, if ClassicSingleton implements thejava.io.Serializable interface, the class's instances can be serialized and deserialized. However, if you serialize a singleton object and subsequently deserialize that object more than once, you will have multiple singleton instances.

Finally, and perhaps most important, Example 1's ClassicSingleton class is not thread-safe. If two threads—we'll call them Thread 1 and Thread 2—callClassicSingleton.getInstance() at the same time, two ClassicSingleton instances can be created if Thread 1 is preempted just after it enters the if block and control is subsequently given to Thread 2.

As you can see from the preceding discussion, although the Singleton pattern is one of the simplest design patterns, implementing it in Java is anything but simple. The rest of this article addresses Java-specific considerations for the Singleton pattern, but first let's take a short detour to see how you can test your singleton classes.

Test singletons

Throughout the rest of this article, I use JUnit in concert with log4j to test singleton classes. If you are not familiar with JUnit or log4j, see Resources.

Example 2 lists a JUnit test case that tests Example 1's singleton:

Example 2. A singleton test case

import org.apache.log4j.Logger;
import junit.framework.Assert;
import junit.framework.TestCase;
public class SingletonTest extends TestCase {
   private ClassicSingleton sone = null, stwo = null;
   private static Logger logger = Logger.getRootLogger();
   public SingletonTest(String name) {
      super(name);
   }
   public void setUp() {
      logger.info("getting singleton...");
      sone = ClassicSingleton.getInstance();
      logger.info("...got singleton: " + sone);
      logger.info("getting singleton...");
      stwo = ClassicSingleton.getInstance();
      logger.info("...got singleton: " + stwo);
   }
   public void testUnique() {
      logger.info("checking singletons for equality");
      Assert.assertEquals(true, sone == stwo);
   }
}

Example 2's test case invokes ClassicSingleton.getInstance()twice and stores the returned references in member variables. The testUnique() method checks to see that the references are identical. Example 3 shows that test case output:

Example 3. Test case output

Buildfile: build.xml
init:
     [echo] Build 20030414 (14-04-2003 03:08)
compile:
run-test-text:
     [java] .INFO main: getting singleton...
     [java] INFO main: created singleton: Singleton@e86f41
     [java] INFO main: ...got singleton: Singleton@e86f41
     [java] INFO main: getting singleton...
     [java] INFO main: ...got singleton: Singleton@e86f41
     [java] INFO main: checking singletons for equality
     [java] Time: 0.032
     [java] OK (1 test)

As the preceding listing illustrates, Example 2's simple test passes with flying colors—the two singleton references obtained with ClassicSingleton.getInstance() are indeed identical; however, those references were obtained in a single thread. The next section stress-tests our singleton class with multiple threads.

Multithreading considerations

Example 1's ClassicSingleton.getInstance() method is not thread-safe because of the following code:

1: if(instance == null) {
2:    instance = new Singleton();
3: }

If a thread is preempted at Line 2 before the assignment is made, the instance member variable will still be null, and another thread can subsequently enter the if block. In that case, two distinct singleton instances will be created. Unfortunately, that scenario rarely occurs and is therefore difficult to produce during testing. To illustrate this thread Russian roulette, I've forced the issue by reimplementing Example 1's class. Example 4 shows the revised singleton class:

Example 4. Stack the deck

import org.apache.log4j.Logger;
public class Singleton {
  private static Singleton singleton = null;
  private static Logger logger = Logger.getRootLogger();
  private static boolean firstThread = true;
  protected Singleton() {
    // Exists only to defeat instantiation.
  }
  public static Singleton getInstance() {
     if(singleton == null) {
        simulateRandomActivity();
        singleton = new Singleton();
     }
     logger.info("created singleton: " + singleton);
     return singleton;
  }
  private static void simulateRandomActivity() {
     try {
        if(firstThread) {
           firstThread = false;
           logger.info("sleeping...");
           // This nap should give the second thread enough time
           // to get by the first thread.
             Thread.currentThread().sleep(50);
       }
     }
     catch(InterruptedException ex) {
        logger.warn("Sleep interrupted");
     }
  }
}

Example 4's singleton resembles Example 1's class, except the singleton in the preceding listing stacks the deck to force a multithreading error. The first time thegetInstance() method is called, the thread that invoked the method sleeps for 50 milliseconds, which gives another thread time to call getInstance() and create a new singleton instance. When the sleeping thread awakes, it also creates a new singleton instance, and we have two singleton instances. Although Example 4's class is contrived, it stimulates the real-world situation where the first thread that calls getInstance() gets preempted.

Example 5 tests Example 4's singleton:

Example 5. A test that fails

import org.apache.log4j.Logger;
import junit.framework.Assert;
import junit.framework.TestCase;
public class SingletonTest extends TestCase {
   private static Logger logger = Logger.getRootLogger();
   private static Singleton singleton = null;
   public SingletonTest(String name) {
      super(name);
   }
   public void setUp() {
      singleton = null;
   }
   public void testUnique() throws InterruptedException {
      // Both threads call Singleton.getInstance().
      Thread threadOne = new Thread(new SingletonTestRunnable()),
             threadTwo = new Thread(new SingletonTestRunnable());
      threadOne.start();
      threadTwo.start();
      threadOne.join();
      threadTwo.join();
   }
   private static class SingletonTestRunnable implements Runnable {
      public void run() {
         // Get a reference to the singleton.
         Singleton s = Singleton.getInstance();
         // Protect singleton member variable from
         // multithreaded access.
         synchronized(SingletonTest.class) {
            if(singleton == null) // If local reference is null...
               singleton = s;     // ...set it to the singleton
         }
         // Local reference must be equal to the one and
         // only instance of Singleton; otherwise, we have two
                  // Singleton instances.
         Assert.assertEquals(true, s == singleton);
      }
   }
}

Example 5's test case creates two threads, starts each one, and waits for them to finish. The test case maintains a static reference to a singleton instance, and each thread callsSingleton.getInstance(). If the static member variable has not been set, the first thread sets it to the singleton obtained with the call to getInstance(), and the static member variable is compared to the local variable for equality.

Here's what happens when the test case runs: The first thread calls getInstance(), enters the if block, and sleeps. Subsequently, the second thread also calls getInstance() and creates a singleton instance. The second thread then sets the static member variable to the instance it created. The second thread checks the static member variable and the local copy for equality, and the test passes. When the first thread awakes, it also creates a singleton instance, but that thread does not set the static member variable (because the second thread has already set it), so the static variable and the local variable are out of synch, and the test for equality fails. Example 6 lists Example 5's test case output:

Example 6. Example 5's output

Buildfile: build.xml
init:
     [echo] Build 20030414 (14-04-2003 03:06)
compile:
run-test-text:
INFO Thread-1: sleeping...
INFO Thread-2: created singleton: Singleton@7e5cbd
INFO Thread-1: created singleton: Singleton@704ebb
junit.framework.AssertionFailedError: expected:<true> but was:<false>
   at junit.framework.Assert.fail(Assert.java:47)
   at junit.framework.Assert.failNotEquals(Assert.java:282)
   at junit.framework.Assert.assertEquals(Assert.java:64)
   at junit.framework.Assert.assertEquals(Assert.java:149)
   at junit.framework.Assert.assertEquals(Assert.java:155)
   at SingletonTest$SingletonTestRunnable.run(Unknown Source)
   at java.lang.Thread.run(Thread.java:554)
     [java] .
     [java] Time: 0.577
     [java] OK (1 test)

Now that we know Example 4's singleton is not thread-safe, let's see how we can fix it.

Synchronization

Making Example 4's singleton class thread-safe is easy—just synchronize thegetInstance() method like this:

public synchronized static Singleton getInstance() {
   if(singleton == null) {
      simulateRandomActivity();
      singleton = new Singleton();
   }
   logger.info("created singleton: " + singleton);
   return singleton;
}

After we synchronize the getInstance() method, we can rerun Example 5's test case with the following results:

Buildfile: build.xml
init:[echo]Build20030414(14-04-200303:15)
compile:[javac]Compiling2 source files
run-test-text:
INFO Thread-1: sleeping...
INFO Thread-1: created singleton:Singleton@ef577d
INFO Thread-2: created singleton:Singleton@ef577d[java].[java]Time:0.513[java] OK (1 test)

This time, the test case works and our multithreading worries are over; however, the astute reader may realize that the getInstance() method only needs to be synchronized the first time it is called. Because synchronization is very expensive performance-wise (synchronized methods can run up to 100 times slower than unsynchronized methods), perhaps we can introduce a performance enhancement that only synchronizes the singleton assignment in getInstance().

A performance enhancement

In search of a performance enhancement, you might choose to rewrite thegetInstance() method like this:

public static Singleton getInstance() {
   if(singleton == null) {
      synchronized(Singleton.class) { 
         singleton = new Singleton();
      }
   }
   return singleton;
}

Instead of synchronizing the entire method, the preceding code fragment only synchronizes the critical code. However, the preceding code fragment is not thread-safe. Consider the following scenario: Thread 1 enters the synchronized block, and, before it can assign the singletonmember variable, the thread is preempted. Subsequently, another thread can enter the if block. The second thread will wait for the first thread to finish, but we will still wind up with two distinct singleton instances. Is there a way to fix this problem? Read on.

Double-checked locking

Double-checked locking is a technique that, at first glance, appears to make lazy instantiation thread-safe. That technique is illustrated in the following code fragment:

public static Singleton getInstance() {
  if(singleton == null) {
     synchronized(Singleton.class) {
       if(singleton == null) {
         singleton = new Singleton();
       }
    }
  }
  return singleton;
}

What happens if two threads simultaneously access getInstance()? Imagine Thread 1 enters the synchronized block and is preempted. Subsequently, a second thread enters the if block. When Thread 1 exits the synchronized block, Thread 2 makes a second check to see if the singleton instance is still null. Since Thread 1 set thesingleton member variable, Thread 2's second check will fail, and a second singleton will not be created. Or so it seems.

Unfortunately, double-checked locking is not guaranteed to work because the compiler is free to assign a value to thesingleton member variable before the singleton's constructor is called. If that happens, Thread 1 can be preempted after the singleton reference has been assigned, but before the singleton is initialized, so Thread 2 can return a reference to an uninitialized singleton instance.

Since double-checked locking is not guaranteed to work, you must synchronize the entire getInstance() method. However, another alternative is simple, fast, and thread-safe.

An alternative thread-safe singleton implementation

Example 7 lists a simple, fast, and thread-safe singleton implementation:

Example 7. A simple singleton

public class Singleton {
   public final static Singleton INSTANCE = new Singleton();
   private Singleton() {
         // Exists only to defeat instantiation.
      }
}

The preceding singleton implementation is thread-safe because static member variables created when declared are guaranteed to be created the first time they are accessed. You get a thread-safe implementation that automatically employs lazy instantiation; here's how you use it:

Singleton singleton = Singleton.INSTANCE;
      singleton.dothis();
      singleton.dothat();
      ...
Of course, like nearly everything else, the preceding singleton is a compromise; if you use that implementation, you can't change your mind and allow multiple singleton instances later on. With a more conservative singleton implementation, instances are obtained through agetInstance() method, and you can change those methods to return a unique instance or one of hundreds. You can't do the same with a public static member variable.

You can safely use Example 7's singleton implementation or Example 1's implementation with a synchronizedgetInstance() method. However, we must explore another issue: You must specify the singleton class at compile time, which is not very flexible. A registry of singletons will let us specify singleton classes at runtime.

Use a registry

Use a singleton registry to:

  • Specify singleton classes at runtime
  • Prevent singleton subclasses from allowing multiple instances

Example 8 lists a singleton class that maintains a registry of singletons, registered by class name:

Example 8. A singleton with a registry

import java.util.HashMap;
import org.apache.log4j.Logger;
public class Singleton {
   private static HashMap map = new HashMap();
   private static Logger logger = Logger.getRootLogger();
   protected Singleton() {
      // Exists only to thwart instantiation
   }
   public static synchronized Singleton getInstance(String classname) {
      if(classname == null) throw new IllegalArgumentException("Illegal classname");
         Singleton singleton = (Singleton)map.get(classname);
      if(singleton != null) {
         logger.info("got singleton from map: " + singleton);
         return singleton;
      }
      if(classname.equals("SingeltonSubclass_One"))
            singleton = new SingletonSubclass_One();         
         else if(classname.equals("SingeltonSubclass_Two"))
            singleton = new SingletonSubclass_Two();
      map.put(classname, singleton);
      logger.info("created singleton: " + singleton);
      return singleton;
   }
   // Assume functionality follows that's attractive to inherit
}

The preceding base class creates subclass instances and stores them in a map. But that base class is high maintenance because you must update its getInstance()method for every subclass. Luckily, we can use reflection to skirt that issue.

Use reflection

Example 9 lists a singleton with a registry that uses reflection to instantiate a particular class's objects. With this implementation, as opposed to Example 8, theSingleton.getInstance() method does not need to update when new subclasses are implemented.

Example 9. Use reflection to instantiate singletons

import java.util.HashMap;
import org.apache.log4j.Logger;
public class Singleton {
   private static HashMap map = new HashMap();
   private static Logger logger = Logger.getRootLogger();
   protected Singleton() {
      // Exists only to thwart instantiation
   }
   public static synchronized Singleton getInstance(String classname) {
      Singleton singleton = (Singleton)map.get(classname);
      if(singleton != null) {
         logger.info("got singleton from map: " + singleton);
         return singleton;
      }
      try {
         singleton = (Singleton)Class.forName(classname).newInstance();
      }
      catch(ClassNotFoundException cnf) {
         logger.fatal("Couldn't find class " + classname);    
      }
      catch(InstantiationException ie) {
         logger.fatal("Couldn't instantiate an object of type " + classname);    
      }
      catch(IllegalAccessException ia) {
         logger.fatal("Couldn't access class " + classname);    
      }
      map.put(classname, singleton);
      logger.info("created singleton: " + singleton);
      return singleton;
   }
}

One more thing concerning singleton registries: they should be encapsulated in their own class for maximum reuse.

Example 10. A SingletonRegistry class

import java.util.HashMap;
import org.apache.log4j.Logger;
public class SingletonRegistry {
   public static SingletonRegistry REGISTRY = new SingletonRegistry();
   private static HashMap map = new HashMap();
   private static Logger logger = Logger.getRootLogger();
   protected SingletonRegistry() {
      // Exists to defeat instantiation
   }
   public static synchronized Object getInstance(String classname) {
      Object singleton = map.get(classname);
      if(singleton != null) {
         return singleton;
      }
      try {
         singleton = Class.forName(classname).newInstance();
         logger.info("created singleton: " + singleton);
      }
      catch(ClassNotFoundException cnf) {
         logger.fatal("Couldn't find class " + classname);    
      }
      catch(InstantiationException ie) {
         logger.fatal("Couldn't instantiate an object of type " + 
                       classname);    
      }
      catch(IllegalAccessException ia) {
         logger.fatal("Couldn't access class " + classname);    
      }
      map.put(classname, singleton);
      return singleton;
   }
}

Notice I implemented the SingletonRegistry class as a singleton. I also generalized the registry so it can store and retrieve any type of object. Example 11 shows a Singletonclass that uses the registry:

Example 11. A Singleton class that uses the registry

import java.util.HashMap;
import org.apache.log4j.Logger;
public class Singleton {
   protected Singleton() {
      // Exists only to thwart instantiation.
   }
   public static Singleton getInstance() {
      return (Singleton)SingletonRegistry.REGISTRY.getInstance(classname);
   }
}

The preceding Singleton class uses the registry's singleton instance to retrieve singleton objects by class name.

Now that we've seen how to implement thread-safe singletons and how to use a registry to specify singleton class names at runtime, let's examine how to deal with classloaders and serialization.

Classloaders

Because multiple classloaders are commonly used in many situations—including servlet containers—you can wind up with multiple singleton instances no matter how carefully you've implemented your singleton classes. If you want to make sure the same classloader loads your singletons, you must specify the classloader yourself; for example:

private static Class getClass(String classname) 
                                         throws ClassNotFoundException {
      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
      if(classLoader == null)
         classLoader = Singleton.class.getClassLoader();
      return (classLoader.loadClass(classname));
   }
}
The preceding method tries to associate the classloader with the current thread; if that classloader is null, the method uses the same classloader that loaded a singleton base class. The preceding method can be used instead ofClass.forName().

Serialization

If you serialize a singleton and then deserialize it twice, you will have two instances of your singleton, unless you implement the readResolve() method, like this:

Example 12. A serializable singleton

import org.apache.log4j.Logger;
public class Singleton implements java.io.Serializable {
   public static Singleton INSTANCE = new Singleton();
   protected Singleton() {
      // Exists only to thwart instantiation.
   }
      private Object readResolve() {
            return INSTANCE;
      }
}

The previous singleton implementation returns the lone singleton instance from thereadResolve() method; therefore, whenever the Singleton class is deserialized, it will return the same singleton instance.

Example 13 tests Example 12's singleton:

Example 13. Test a serializable singleton

import java.io.*;
import org.apache.log4j.Logger;
import junit.framework.Assert;
import junit.framework.TestCase;
public class SingletonTest extends TestCase {
   private Singleton sone = null, stwo = null;
   private static Logger logger = Logger.getRootLogger();
   public SingletonTest(String name) {
      super(name);
   }
   public void setUp() {
      sone = Singleton.INSTANCE;
      stwo = Singleton.INSTANCE;
   }
   public void testSerialize() {
      logger.info("testing singleton serialization...");
      writeSingleton();
      Singleton s1 = readSingleton();
      Singleton s2 = readSingleton();
      Assert.assertEquals(true, s1 == s2);
   }
   private void writeSingleton() {
      try {
         FileOutputStream fos = new FileOutputStream("serializedSingleton");
         ObjectOutputStream oos = new ObjectOutputStream(fos);
         Singleton s = Singleton.INSTANCE;
         oos.writeObject(Singleton.INSTANCE);
         oos.flush();
      }
      catch(NotSerializableException se) {
         logger.fatal("Not Serializable Exception: " + se.getMessage());
      }
      catch(IOException iox) {
         logger.fatal("IO Exception: " + iox.getMessage());
      }
   }
   private Singleton readSingleton() {
      Singleton s = null;
      try {
         FileInputStream fis = new FileInputStream("serializedSingleton");
         ObjectInputStream ois = new ObjectInputStream(fis);
         s = (Singleton)ois.readObject();
      }
      catch(ClassNotFoundException cnf) {
         logger.fatal("Class Not Found Exception: " + cnf.getMessage());
      }
      catch(NotSerializableException se) {
         logger.fatal("Not Serializable Exception: " + se.getMessage());
      }
      catch(IOException iox) {
         logger.fatal("IO Exception: " + iox.getMessage());
      }
      return s;
   }
   public void testUnique() {
      logger.info("testing singleton uniqueness...");
      Singleton another = new Singleton();
      logger.info("checking singletons for equality");
      Assert.assertEquals(true, sone == stwo);
   }
}

The preceeding test case serializes Example 12's singleton and deserializes it twice. Then the test case checks to see if the deserialized singletons are the same object. Here's the test case output:

Buildfile: build.xml
init:
     [echo] Build 20030422 (22-04-2003 11:32)
compile:
run-test-text:
     [java] .INFO main: testing singleton serialization...
     [java] .INFO main: testing singleton uniqueness...
     [java] INFO main: checking singletons for equality
     [java] Time: 0.1
     [java] OK (2 tests)

Singleton sign-off

The Singleton pattern is deceivingly simple, especially for Java developers. In this article, I've demonstrated how Java developers implement singletons, considering multithreading, classloaders, and serialization. I've also shown how you can implement singleton registries that let you specify singleton classes at runtime.

David Geary is the author of Core JSTL Mastering the JSP Standard Tag Library (Prentice Hall, 2002; ISBN: 0131001531), Advanced JavaServer Pages (Prentice Hall, 2001; ISBN: 0130307041), and the Graphic Java series (Prentice Hall). David has been developing object-oriented software with numerous object-oriented languages for 18 years. Since the GOF Design Patterns book was published in 1994, David has been an active proponent of design patterns, and has used and implemented design patterns in Smalltalk, C++, and Java. In 1997, David began working full-time as an author and occasional speaker and consultant. David is a member of the expert groups defining the JSP Standard Tag Library and JavaServer Faces, and is a contributor to the Apache Struts JSP framework.

Learn more about this topic

原文地址:https://www.cnblogs.com/davidwang456/p/3642501.html