The Source for Java Technology Collaboration
User: Password:



Kirill Grouchnikov

Kirill Grouchnikov's Blog

How single can your singleton instance be?

Posted by kirillcool on August 24, 2005 at 01:40 AM | Comments (12)

We are all familiar with the following straightforward implementation of lazy-loaded Singleton pattern:
    
public class TestSingleton {
  private static TestSingleton instance;

  private TestSingleton() {}

  public static synchronized TestSingleton getInstance() {
    if (instance == null) {
      instance = new TestSingleton();
    }
    return instance;
  }
}
The constructor is private, and a single public function is exposed. This function (which is also synchronized) tests whether there is already an instance and if not, it creates one. Item 57 in Joshua Bloch's "Effective Java" instructs us to add readResolve() function in case the singleton is also Serializable. Another version, which employs eager-loading is:
    
public class TestSingleton {
  private static TestSingleton instance = new TestSingleton();

  private TestSingleton() {}

  public static TestSingleton getInstance() {
    return instance;
  }
}
It's even simpler than the previous one. You pay a one-time penalty of creating an object (even if you never use it), but the getInstance() is no longer synchronized.

The constructor, is of course, private in both cases to prevent accessing it from the outside code. However, as shown before, you can use combination of getDeclaredConstructors and setAccessible to make the private constructor accessible via reflection. You can then create any number of instances of that singleton class. However, the author of the above failed to notice that the created instance may not be fully functional. The publically-accessible getInstance() function can perform additional manipulations with the created object, setting additional properties and attributes. In this case, the object you have may not function properly. Here, you need to call the getInstance() function and have it return a different instance (properly and fully initialized) each time you call it.

The answer lies in Chapter 2 of excellent Component Development for the Java Platform book (which came out in 2001). This chapter describes the way the ClassLoaders work, and how you can have any number of Class object instances for the same class. Let's start with a small (and not real-life) example to show how it works, and then go to a real-life example.

First, we create a new Java class at runtime:
    
public class SingletonMain {
   public static void main(String... args) throws Exception {
      Logger _logger = Logger.getLogger(SingletonMain.class.getName());
      // create first version of class
      String className = "TestSingleton";
      String dir1 = "C:\\temp\\ver1";
      new File(dir1).mkdirs();
      BufferedWriter bw1 = new BufferedWriter(new FileWriter(dir1
            + File.separator + className + ".java"));
      bw1.write("public class " + className + " {");
      bw1.newLine();
      bw1.write("  private static " + className + " instance;");
      bw1.newLine();
      bw1.write("  private " + className + "() {}");
      bw1.newLine();
      bw1.write("  public static synchronized " + className + " getInstance() {");
      bw1.newLine();
      bw1.write("    if (instance == null) {");
      bw1.newLine();
      bw1.write("      instance = new " + className + "();");
      bw1.newLine();
      bw1.write("    }");
      bw1.newLine();
      bw1.write("    return instance;");
      bw1.newLine();
      bw1.write("  }");
      bw1.newLine();
      bw1.write("}");
      bw1.newLine();
      bw1.close();
The class looks like this:
    
public class TestSingleton {
  private static TestSingleton instance;
  private TestSingleton() {}
  public static synchronized TestSingleton getInstance() {
    if (instance == null) {
      instance = new TestSingleton();
    }
    return instance;
  }
}
We now compile it and use URLClassLoader to load the compiled class:
    
      // compile
      int compStatus = com.sun.tools.javac.Main.compile(new String[] { dir1
            + File.separator + className + ".java" });
      _logger.info("Compilation status is " + compStatus);

      // load
      ClassLoader classLoader1 = new URLClassLoader(
            new URL[] { new File(dir1).toURL() });
      Class clazz1 = classLoader1.loadClass(className);
      _logger.info("Loaded " + clazz1.getName() + " [" + clazz1.hashCode()
            + "]");
The output is
    
INFO [11:05:27.553] [SingletonMain.main] Compilation status is 0
INFO [11:05:27.584] [SingletonMain.main] Loaded TestSingleton [24287316]
Now, we use reflection to call the getInstance() function. In addition, we test that no public constructor is defined:
    
      Method getter1 = clazz1.getMethod("getInstance", new Class[0]);
      Constructor[] ctrs1 = clazz1.getConstructors();
      
      _logger.info(ctrs1.length + " constructors");
      Object val10 = getter1.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val10.hashCode());
      Object val11 = getter1.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val11.hashCode());
      Object val12 = getter1.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val12.hashCode());
      
The result here is, as expected, the same instance on all calls to getInstance():
    
INFO [11:05:27.584] [SingletonMain.main] 0 constructors
INFO [11:05:27.584] [SingletonMain.main] Fetched object #7254922
INFO [11:05:27.584] [SingletonMain.main] Fetched object #7254922
INFO [11:05:27.584] [SingletonMain.main] Fetched object #7254922
As mentioned before, you can use getDeclaredConstructors() and setAccessible() on Constructor to make it accessible, but then you will miss the additional logic (empty in our sample case) in getInstance().

In order to create a second instance of TestSingleton class, we need to create additional ClassLoader that does not have our first ClassLoader as its ascendant. As described in the book, the ClassLoader first checks its cache to see if it already loaded the specified class, then asks its parent for the class (recursively until there is no parent), and only then (if parent tells that it doesn't have this class) it loads the class (from the URL in our case). Here, we need to copy the compiled class to another directory and create a second URLClassLoader. Both class loaders will have the same parent (system class loader), but this parent doesn't have the TestSingleton class, hence the second class loader will have its own copy:
    
      // copy the class file to another directory
      String dir2 = "C:\\temp\\ver2";
      new File(dir2).mkdirs();
      InputStream in = new FileInputStream(new File(dir1 + File.separator
            + className + ".class"));
      OutputStream out = new FileOutputStream(new File(dir2 + File.separator
            + className + ".class"));
      byte[] buf = new byte[1024];
      int len;
      while ((len = in.read(buf)) > 0) {
         out.write(buf, 0, len);
      }
      in.close();
      out.close();

      // load
      ClassLoader classLoader2 = new URLClassLoader(
            new URL[] { new File(dir2).toURL() });
      Class clazz2 = classLoader2.loadClass(className);
      _logger.info("Loaded " + clazz2.getName() + " [" + clazz2.hashCode()
            + "]");
The result is:
    
INFO [11:05:27.600] [SingletonMain.main] Loaded TestSingleton [33482492]
As you can see, the hash code of the second class object is different - we have a distinct class. Let's test that it's really different:
    
      Method getter2 = clazz2.getMethod("getInstance", new Class[0]);
      Constructor[] ctrs2 = clazz1.getConstructors();

      _logger.info(ctrs2.length + " constructors");
      Object val20 = getter2.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val20.hashCode());
      Object val21 = getter2.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val21.hashCode());
      Object val22 = getter2.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val22.hashCode());
And the result is:
    
INFO [11:05:27.600] [SingletonMain.main] 0 constructors
INFO [11:05:27.600] [SingletonMain.main] Fetched object #32429958
INFO [11:05:27.600] [SingletonMain.main] Fetched object #32429958
INFO [11:05:27.600] [SingletonMain.main] Fetched object #32429958
So, now we have two instances of (almost) the same class. It is true that the Class objects are different, but the functionality is the same.

Now that we have seen the technique, it can be trivially applied to already existing classes. In order to do this, we scan the class path looking for either the .class on local computer or for jar file that contains the required class. After we find it, we copy the corresponding class to some directory, and load it using the same technique as above (passing null parent). Afterwards we create a second copy in another directory.
    
public class SingletonMain2 {
   public static void main(String... args) throws Exception {
      Logger _logger = Logger.getLogger(SingletonMain2.class.getName());

      String className = "TestSingleton2";
      String classSig = className + ".class";
      File dir1 = new File("C:\\temp\\ver1");
      dir1.mkdirs();
Now we start to scan the classpath. Note that here we show only Windows version. For cross-platform, you need to use ":;" pattern to break the classpath and assemble drive names back when you are under Windows:
    
      String classpath = System.getProperty("java.class.path");
      _logger.info("Classpath : " + classpath);
      String[] pathComps = classpath.split(";");
      boolean wasLocated = false;
For each classpath component, we check whether it is a jar file or a directory. The handling of jar files is left as a (simple) exercise:
    
      for (String pathComp : pathComps) {
         if (wasLocated)
            break;
         if (pathComp.endsWith(".jar")) {
            // scan jar and look for the desired class.
            // Use JarInputStream for reading jar file and
            // JarEntry for checking the current entry.
            // The, getInputStream() on the matching entry
            // and copy it to the temp directory as below.
         } 
In case it's a directory, we scan it for the desired .class file (not that if the class belongs to some package, you'll need to adjust the scan code correspondingly):
    
            // should be directory
            File pathCompDir = new File(pathComp);
            if (pathCompDir.exists() && pathCompDir.isDirectory()) {
               // scan for the class
               File[] allFiles = pathCompDir.listFiles();
               for (File currFile : allFiles) {
                  if (currFile.getName().equals(classSig)) {
                     _logger.info("Located at "
                           + currFile.getAbsolutePath());
                     wasLocated = true;
                     // copy to another directory
                     InputStream in = new FileInputStream(currFile);
                     OutputStream out = new FileOutputStream(new File(
                           dir1, classSig));
                     byte[] buf = new byte[1024];
                     int len;
                     while ((len = in.read(buf)) > 0) {
                        out.write(buf, 0, len);
                     }
                     in.close();
                     out.close();
                     break;
                  }
               }
            }
As shown above, when the class is found, it is copied to a temporary directory. After we are done copying, we create the first URLClassLoader with null parent. This is crucial - the default parent of a new class loader is the system class loader. In this case, the parent class loader has access to the original class (since it's in the classpath), and the copied class will be simply ignored.
    
      if (!wasLocated) {
         _logger.info("Not located - quitting");
      }

      // very important - pass null parent class loader, otherwise
      // the parent class loader will be system class loader and
      // it will find the class in the classpath
      ClassLoader classLoader1 = new URLClassLoader(
            new URL[] { dir1.toURL() }, null);
      Class clazz1 = classLoader1.loadClass(className);
      _logger.info("Loaded " + clazz1.getName() + " [" + clazz1.hashCode()
            + "]");
      _logger.info("Have " + clazz1.getName() + " [" + clazz1.hashCode()
            + "]");
The result is
    
INFO [11:27:39.598] [SingletonMain2.main] Loaded TestSingleton2 [4384790]
INFO [11:27:39.598] [SingletonMain2.main] Have TestSingleton2 [4384790]
Now, as before we call getInstance() a couple of times:
    
      Method getter1 = clazz1.getMethod("getInstance", new Class[0]);
      Constructor[] ctrs1 = clazz1.getConstructors();

      _logger.info(ctrs1.length + " constructors");
      Object val10 = getter1.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val10.hashCode());
      Object val11 = getter1.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val11.hashCode());
      Object val12 = getter1.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val12.hashCode());
And see that the same instance is returned:
    
INFO [11:27:39.598] [SingletonMain2.main] 0 constructors
INFO [11:27:39.598] [SingletonMain2.main] Fetched object #9634993
INFO [11:27:39.598] [SingletonMain2.main] Fetched object #9634993
INFO [11:27:39.598] [SingletonMain2.main] Fetched object #9634993
Now, we copy the same class to another temporary directory
    
      // copy the class file to another directory
      String dir2 = "C:\\temp\\ver2";
      new File(dir2).mkdirs();
      InputStream in = new FileInputStream(new File(dir1 + File.separator
            + className + ".class"));
      OutputStream out = new FileOutputStream(new File(dir2 + File.separator
            + className + ".class"));
      byte[] buf = new byte[1024];
      int len;
      while ((len = in.read(buf)) > 0) {
         out.write(buf, 0, len);
      }
      in.close();
      out.close();
And create additional class loader:
    
      // load
      ClassLoader classLoader2 = new URLClassLoader(
            new URL[] { new File(dir2).toURL() }, null);
      Class clazz2 = classLoader2.loadClass(className);
      _logger.info("Loaded " + clazz2.getName() + " [" + clazz2.hashCode()
            + "]");
The class object is different:
    
INFO [11:27:39.614] [SingletonMain2.main] Loaded TestSingleton2 [14576877]
Call getInstance() a couple of times on the new class:
    
      Method getter2 = clazz2.getMethod("getInstance", new Class[0]);
      Constructor[] ctrs2 = clazz2.getConstructors();

      _logger.info(ctrs2.length + " constructors");
      Object val20 = getter2.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val20.hashCode());
      Object val21 = getter2.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val21.hashCode());
      Object val22 = getter2.invoke(null, new Object[0]);
      _logger.info("Fetched object #" + val22.hashCode());
And you get the same (but different from the first) object:
    
INFO [11:27:39.676] [SingletonMain2.main] 0 constructors
INFO [11:27:39.676] [SingletonMain2.main] Fetched object #12677476
INFO [11:27:39.676] [SingletonMain2.main] Fetched object #12677476
INFO [11:27:39.676] [SingletonMain2.main] Fetched object #12677476
And now, the big question - how single can your singleton instance be? See here how to create a true singleton.
Technorati Profile

Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • Effective Java, Item 48 gives a very much better way to initialise singletons.

    Posted by: tackline on August 24, 2005 at 05:05 AM

  • tackline,
    The point is not how to initialize singleton, but how to preserve that it's a real singleton. The initialize-on-demand holder will break as easily under the above scenario:

    private static class FooHolder {
    static final Foo foo = new Foo();
    }
    public static Foo getFoo() { return FooHolder.foo; }

    In case both Foo and FooHolder are taken from the class path and put under two directories, the following will happen:

    FooHolder is loaded from the specified directory.
    Foo is implicitly loaded, using the same class loader (not the system class loader)
    Foo is loaded from the same directory

    Posted by: kirillcool on August 24, 2005 at 05:25 AM

  • Near the top of your post, you compare the lazy loaded version of the singleton to the eager version that uses a static initialize. You comment:
    It's even simpler than the previous one. You pay a one-time penalty of creating an object (even if you never use it), but the getInstance() is no longer synchronized.

    Infact, this almost never matters in practice, since people almost always write this:


    TestSingleton t = TestSingleton.getInstance();
    //do something with t


    It's largely irrelevant whether the getInstance() method lazy loads the singleton, or whether the static initializer does, because people almost always refer to the class for the first time and call getInstance() on one line, as shown.

    So you're paying the synchronization penalty for every access to getInstance(), with no discernable benefit.

    The only time if would matter, is if TestSingleton had some other static method:


    class TestSingleton {

    ...

    public static void foo() {
    //do something that doesn't itself involve calling getInstance()
    }


    Now if someone writes this code:


    TestSingleton.foo();


    Then there's an advantage to the lazy loaded form because you can call foo() without creating the singleton.

    However, I submit that it's far from common to find singletons with unrelated static methods like this, and as such 99% of the time you should avoid the lazy loaded version and just initialize in a static block. Thus the common case (calling getInstance()) is faster because you don't have to synchronize every time.

    Posted by: lordpixel on August 24, 2005 at 07:03 AM

  • lordpixel,
    Although technically correct, the comment misses the point as well. It doesn't matter whether you use lazy or eager creation. I have tested the ClassLoader trick on both versions and both of them failed protecting the "singleton-ness" of the class.

    Posted by: kirillcool on August 24, 2005 at 07:08 AM

  • kirillcool - oh absolutely, I didn't mean to imply there isn't a problem with classloaders and singletons.

    If you've ever worked on the J2EE side then you'll often see this sort of problem because J2EE servers always have multiple classloaders. I've seen people use JNDI as a way to try to ensure singleton-ness to get around this.

    My main point is that I'm not sure why people are so fond of the lazy initialization version (and it's evil cousin, broken double-checked locking). Everyone gives it as the standard answer when static holder or simple static initialization are usually as good or better. But you're compeltely correct about the classloader thing.

    One caveat - what I said about the static initialized version is true for the Sun VM. JRockit and the old MS VM have somewhat different classloading behaviour. The static holder approach is safer because it is less dependent on classloading being implemented like the Sun VM happens to do it than simple static initialization

    In any case, don't use Singletons, use dependency injection :)

    Posted by: lordpixel on August 24, 2005 at 11:37 AM

  • This all presumes that somehow there is EVER a need for a real singleton. I can't seem to see why a true singleton would ever be an interesting thing. Maybe you need a controlled way to instantiate objects or a well defined method for looking up a "singleton" instance, but a true enforced singleton is just silly. Singleton is a pattern of USAGE not of DESIGN. It makes sense to create a single instance of an object and to use a single instance, but designing it into the class and jumping through hoops to enforce it is just silly.

    This is a fascinating intellectual exercise, but I pity the developer who has to work on a project where someone does something like this.

    Posted by: orb on August 24, 2005 at 07:19 PM

  • And also, when you're using custom classloaders to get around restrictions on classes (like singleton behaviour) then in my book you're on your own and good luck if you're later trying to rely on that singleton behaviour!

    Posted by: jwenting on August 25, 2005 at 03:00 AM

  • jwenting,
    This is an example of intentionally malicious attempt to break the singleton implementation to try and corrupt something in your data. The "buffer-overflow" techniques in C and C++ can also be categorized as being on "your own", however they are used to gain control of a system. This is, of course, an artificial comparison, but nevertheless.
    Suppose the code in your singleton object really depends that it's a singleton - let's say a connection to authorization service. This object is supposed to have some HashMap of authorization tokens with a single-sign-on implementation. Now, if you have another object, you may force the user to login once again, and here you may:

    cause the authorization repository to go to undefined state
    sniff the packet with the authorization data (since the author never encrypted the packets, thinking that it's a real singleton, and the login always happens in a place he assumed to)
    ...

    Posted by: kirillcool on August 25, 2005 at 03:32 AM

  • You can break everything using reflection and classloaders.
    That's what the SecurityManager is for.

    Posted by: joergwassmer on August 25, 2005 at 05:33 AM

  • joerg,
    How about this:

    private void f() {
    // do something
    }

    public void g() {
    f();
    // do something else
    }

    Reflection will not help you call f() from g(). In this case you will have to use bytecode libraries to retrieve the additional code from g(). And how do you propose to use SecurityManager in someone else's environment?

    Posted by: kirillcool on August 25, 2005 at 05:58 AM

  • What are you trying to prove?

    If I can route a user onto my second authorization "singleton", why don't I just write my own bean that simply consumes the authorization data?

    Or why don't I just start a second VM? There goes your singleton, too.

    Posted by: mernst on August 26, 2005 at 08:09 AM

  • If you are interested in Singletons you might like:

    http://pec.dev.java.net/

    It provides compiler support for Singletons (not the sort that are multiple loader safe though)

    Posted by: hlovatt on September 01, 2005 at 06:18 PM





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds