Java: Lazy Initializing Singleton

前端 未结 5 644
情书的邮戳
情书的邮戳 2020-12-10 16:14

The pattern to create singletons seems to be something like:

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


        
相关标签:
5条回答
  • 2020-12-10 16:46

    How about you lazy initialize in the build phase where you execute the unit tests. Then you change the code back to inline initialize before it's compiled for distribution.

    Your production code is inline initialized, except during your tests. Perhaps this discrepancy btw production and testing code could introdude bugs, but which?

    (Of course if this is a solution, we let a build phase + tool do the work. I see this facilitated with maven and dp4j).

    0 讨论(0)
  • 2020-12-10 16:54

    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.

    0 讨论(0)
  • 2020-12-10 16:55

    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.

    0 讨论(0)
  • 2020-12-10 16:56

    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
    
    0 讨论(0)
  • 2020-12-10 17:05

    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.

    0 讨论(0)
提交回复
热议问题