4

I have a class that might throw any run-time exceptions during initialization. I want the class to be a singleton since the cost of keeping several objects in memory is high. I am using that class in another class.

My use case is as follows:

  • I have to use a single instance of Controller.
  • Each instance of Parent must use the same Controller instance.
  • Controller constructor might throw exceptions.
  • If instantiation fails, I should retry to instantiate after sometime.

So I check if my Controller instance is null when I try to do a "get" on the Controller, if yes, I try to instantiate it again.

Following is my code:

class Parent
{
    private static volatile Controller controller;
    private static final Object lock = new Object();

    static
    {
        try
        {
            controller = new Controller();
        }
        catch(Exception ex)
        {
            controller = null;
        }
    }

    private Controller getController() throws ControllerInstantiationException
    {
        if(controller == null)
        {
            synchronized(lock)
            {
                if(controller == null)
                {
                    try
                    {
                        controller = new Controller();
                    }
                    catch(Exception ex)
                    {
                        controller = null;
                        throw new ControllerInstatntationException(ex);
                    }
                }
            }
        }
        return controller;
    }

    //other methods that uses getController() 
}

My question is, is this code broken? I read somewhere that the above code would be a problem in JVM 1.4 or earlier. Can you provide references/solutions? Please note that I am asking this question because there is a lot of confusion regarding this topic in the internet.

Thanks.

Swaranga Sarma
  • 13,055
  • 19
  • 60
  • 93

5 Answers5

5

I believe it's not broken, cause of volatile declaration. But imho better to avoid code like this. There is no guarantee, that this code will work with Java 8 for example. There are another way to create lazy singleton. I always (almost) use this method. First time faced with it in Java Concurrency in Practice book.

public class Singleton {
        private Singleton() { }

        private static class SingletonHolder { 
                public static final Singleton instance = new Singleton();
        }

        public static Singleton getInstance() {
                return SingletonHolder.instance;
        }
}

I don't know what you are doing in your code, it's hard to say, how to tweak it. The most straightforward way, simply use synchronize method. Do you seriously want to receive some performance benefit using double-check-locking ? Is there bottle-neck in synch method ?

Anton
  • 5,831
  • 3
  • 35
  • 45
  • My bottleneck is the `getController()` method. It is used very frequently. So I do not want to streamline calls to `getController()` unless there is no other option. Also the pain point is the `new Controller()` constructor. That might fail, and if it does I need to retry the instantiation again sometime later when I call `getController()` again. – Swaranga Sarma Feb 15 '12 at 09:27
  • The code snippet that you posted will not work because it only supports one time instantiation. What if the first instantiation fails? – Swaranga Sarma Feb 15 '12 at 09:28
  • @Umar, This example is good for Java 1.4. If you have Java 5.0 using an `enum` is much simpler (see my answer) – Peter Lawrey Feb 15 '12 at 10:26
  • @Peter Lawrey Agree, enum is another way to create singleton. I think OP should review his design and choose another solution. Hard to imagine for me, what OP is doing – Anton Feb 15 '12 at 12:33
  • 1
    Holder pattern or enum is only good if the initializer is guaranteed to run only once. In a case like this, where the OP wants the option of trying again later, careful (using `volatile` properly) double checked locking on a modern JVM (one with the Java Memory Model guarantees) is guaranteed to work. – Avi Feb 15 '12 at 15:59
1

The only thing which is broken is to make the example far more complicated than it needs to be.

All you need is an enum

// a simple lazy loaded, thread safe singleton.
enum Controller {
    INSTANCE
}
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • My Controller cannot be an enum. I cannot change it. Secondly, the Controller() constructor might fail. I will need to try that again. Does the enum solve my second issue? I don't think so. – Swaranga Sarma Feb 15 '12 at 10:39
  • You can change the constructor so it doesn't fail. I would be interested in why you feel unable to change it. Move the code which might fail to later access methods. – Peter Lawrey Feb 15 '12 at 22:07
  • I do not own the code of the Controller class. The controller class is a wrapper to a service. Sometimes while initializing the controller, the service might be busy so the Controller becomes useless and hence might throw a InstantiationException. – Swaranga Sarma Feb 16 '12 at 18:05
1

Using an AtomicBoolean (much like I suggested here) would be safer and allows for repeat attempts at instantiation on failure.

public static class ControllerFactory {
  // AtomicBolean defaults to the value false.
  private static final AtomicBoolean creatingController = new AtomicBoolean();
  private static volatile Controller controller = null;

  // NB: This can return null if the Controller fails to instantiate or is in the process of instantiation by another thread.
  public static Controller getController() throws ControllerInstantiationException {
    if (controller == null) {
      // Stop another thread creating it while I do.
      if (creatingController.compareAndSet(false, true)) {
        try {
          // Can fail.
          controller = new Controller();
        } catch (Exception ex) {
          // Failed init. Leave it at null so we try again next time.
          controller = null;
          throw new ControllerInstantiationException(ex);
        } finally {
          // Not initialising any more.
          creatingController.set(false);
        }
      } else {
        // Already in progress.
        throw new ControllerInstantiationException("Controller creation in progress by another thread.");
      }
    }
    return controller;
  }

  public static class ControllerInstantiationException extends Exception {
    final Exception cause;

    public ControllerInstantiationException(Exception cause) {
      this.cause = cause;
    }

    public ControllerInstantiationException(String cause) {
      this.cause = new Exception(cause);
    }
  }

  public static class Controller {
    private Controller() {
    }
  }
}
Community
  • 1
  • 1
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • In your variant of double-check singleton, does controller need to be volatile as well given the use of AtomicBoolean? – Καrτhικ Sep 02 '14 at 14:39
  • @Καrτhικ - Yes. Although in fact using any atomic establishes a "happens before" barrier for all variables it is not specified that this is so - it is only specified for the specific atomic. In the above code the `controller` could be set to non-null in one thread but the change may never ripple out to other threads if it was not set `volatile`. – OldCurmudgeon Sep 02 '14 at 15:15
1

Yes, it is guaranteed to work by the Java Memory Model on modern JVMs. See the section Under the new Java Memory Model in The "Double-Checked Locking is Broken" Declaration.

As other answers have pointed out, there are simpler singleton patterns, using Holder classes or enums. However, in cases like yours, where you want to allow for trying to reinitialize several times if the first try fails, I believe that double-checked locking with a volatile instance variable is fine.

Avi
  • 19,934
  • 4
  • 57
  • 70
0

It is not an answer to your question but this famous article on Double-Checked Locking is Broken explains well as to why it is broken for java 1.4 or earlier version.

Kuldeep Jain
  • 8,409
  • 8
  • 48
  • 73
  • On the contrary, that article says it *will* work on modern JVMs (1.5 and up), using `volatile` as the OP is doing. – Avi Feb 15 '12 at 15:57