Skip to main content

How single can your singleton instance be?

Posted by kirillcool on August 24, 2005 at 1:40 AM PDT

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

Related Topics >>

Comments

How to make true singleton

Thanks for your detail explanation on singleton. the link you provides for true singleton is not really a singleton when multiple custom class loaders are using the singleton. See the http://joshitech.blogspot.com/2009/09/absolute-singleton.html.