Java: Lazy Initializing Singleton

13,912

Solution 1

You could use the Factory pattern to create the singleton, and switch implementations depending on evironment.

Or, avoid using the singleton pattern, and use Dependency Injection instead.

Solution 2

You can use an enum as a Singleton

enum Singleton {
    INSTANCE;
}

Say your singleton does something undesirable in unit tests, you can;

// in the unit test before using the Singleton, or any other global flag.
System.setProperty("unit.testing", "true");

Singleton.INSTANCE.doSomething();

enum Singleton {
    INSTANCE;
    {
        if(Boolean.getBoolean("unit.testing")) {
           // is unit testing.
        } else {
           // normal operation.
        }
    }
}

Note: there is no synchronised blocks or explicit lock needed. The INSTANCE will not be loaded until the .class is accessed and not initialised until a member is used. provided you only use Singleton.INSTANCE and not Singleton.class there won't be a problem with the value used to initialise changing later.


Edit: if you use just the Singleton.class this might not initialise the class. It doesn't in this example on Java 8 update 112.

public class ClassInitMain {
    public static void main(String[] args) {
        System.out.println("Printing a class reference");
        Class clazz = Singleton.class;
        System.out.println("clazz = " + clazz);
        System.out.println("\nUsing an enum value");
        Singleton instance = Singleton.INSTANCE;
    }

    static enum Singleton {
        INSTANCE;

        Singleton() {
            System.out.println(getClass() + " initialised");
        }
    }
}

prints

Printing a class reference
clazz = class ClassInitMain$Singleton

Using an enum value
class ClassInitMain$Singleton initialised

Solution 3

Double-checked locking is broken in every language, not just Java.

I tend to eschew singletons, but you can use the holder pattern just fine if you need them, as recommended in Josh Bloch's Effective Java:

public class Foo
{
  static class Holder
  {
    static final Foo instance = new Foo();
  }

  public static Foo getInstance()
  {
    return Holder.instance;
  }

  private Foo()
  {
  }

  // ...
}

EDIT: Repaired the reference.

Solution 4

You can dependency inject the singleton instance, override the getInstance() from the unit test code, use aspect oriented programming to intercept the method call and return a different object, or use a tool like jmockit which lets you mock pretty much anything, including statics, final classes, constructors, and all the stuff people normally say is "untestable."

One approach I've taken in legacy systems (where I wanted to make something testable with a minimal impact on the system's architecture) was to modify the factory methods (getInstance) to check a system property for an alternate implementation that I would instantiate instead. This was set to an alternate, mock object in the unit test suite.

As for the "double checked locking is broken" statement, that's not really true anymore, if you use the volatile keyword, and Java >= 1.5. It was broken (even with volatile) with 1.4 and earlier, but if you know your code will be run on only recent JVMs, I wouldn't worry about it. But I also wouldn't use a singleton anyway: having a DI/IOC container manage the lifecycle of the object would solve both of your problems (testability and synchronized accessor bottleneck) much more elegantly.

Share:
13,912

Related videos on Youtube

bluphoenix
Author by

bluphoenix

Updated on June 04, 2022

Comments

  • bluphoenix
    bluphoenix about 2 years

    The pattern to create singletons seems to be something like:

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

    However my problem is how do you Unit with a class like this if the Singleton Constructor does something that is not unit test friendly e.g. calls external service , jndi lookup etc.

    I would think i could refactor it like:

    public class Singleton {
        private static Singleton instance;
        private Singleton(){
        }
    
        public synchronized static Singleton getInstance()
        {
            if(instance == null)
                 instance = new Singleton();
            return instance;
        }
    
         //for the unit tests
         public static void setInstance(Singleton s)
         {
              instancce = s;
         }
    }
    

    The problem now is that just for unit testability I have forced the getInstance to be synchronized so just for testing aspect it will have a negative impact on the real application. Is there a way around it, it seems any other sort of lazy initialization will not work because of the broken nature of double locking pattern in java.

  • bluphoenix
    bluphoenix about 13 years
    hmm I like the idea of a System Property which could flag for unit testing. Ideally it would be nice to change to not use the Singleton but that its a bit above me , large old code base and no time to re-factor :(.
  • Lew Bloch
    Lew Bloch over 7 years
    The instance will be loaded even via a reference to .class, but not initialized.
  • Vishy
    Vishy over 7 years
    @LewBloch There might be JVMs where this happens, but Oracle JVM 8 update 112 doesn't.
  • Lew Bloch
    Lew Bloch over 7 years
    Not correct. The JLS explains. The type loads on the reference to the .class literal, obviously, or else the literal wouldn't be in memory to reference in the first place. That will load the type, but not initialize it. You probably confused loading and initialization.
  • Lew Bloch
    Lew Bloch over 7 years
    Although I mentioned the holder class solution to implementing singletons, that is nearly always overengineering. The simplest, most reliable, and nearly always correct approach is to define an enum with a single constant, as @Peter Lawrey pointed out.
  • Lew Bloch
    Lew Bloch over 7 years
    Reference to the .class by itself was always enough to load a type, but according to the JLS going back to at least Java 1.4, was never supposed to trigger type initialization. Prior to Java 1.5 the Sun JVMs had a bug wherein such references in fact did trigger type initialization. They fixed that bug in Java 5, and since then that feature has worked according to spec.
  • Vishy
    Vishy over 7 years
    @LewBloch hmmm. I have reworded it as it was confusing I admit.

Related