简体   繁体   中英

Singleton with or without holder = lazy vs eager initialisation?

Is this correct:

  • Using a singleton with a holder gives lazy initialisation because the class SingletonHolder is only initialised when Singleton.getInstance() is run. This relies on SingletonHolder only ever being referenced inside Singleton.getInstance() . It's thread safe because the class loader takes care of synchronisation.
  • Using a singleton without the holder is eager initialisation because as soon as Java comes across code that references Singleton , all its static fields are resolved. It's also thread safe because the class loader takes care of synchronisation.

Singleton with a holder.

public class Singleton {
   private static class SingletonHolder {
      private static final Singleton INSTANCE = new Singleton();
   }
   public static Singleton getInstance() {
      return SingletonHolder.INSTANCE;
   }
   private Singleton(){ }
}

Singleton without a holder.

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

Update in response to @jan's suggestion that this is a duplicate of What is an efficient way to implement a singleton pattern in Java? . I disagree. I am not asking what is the best way to do it: I am only asking what makes these two specific implementations lazy vs eager loading. Answers like xyz's broadly address lazy vs eager, but not by contrasting the two examples I was trying to examine (or with the same keywords which is why it never came up in my initial searches).

In response to @Sriram, here is my test to prove which is eager vs lazy loading.

Lazy loading with a holder

public class Singleton {
   private static class SingletonHolder {
      static {
         System.out.println("In SingletonHolder static block.");
      }
      private static final Singleton INSTANCE = new Singleton();
   }

   public static Singleton getInstance() {
      System.out.println("In getInstance().");
      return SingletonHolder.INSTANCE;
   }

   private Singleton() {
      System.out.println("In constructor.");
   }

   private void doSomething() {
      System.out.println("Singleton working.");
   }

   public static void main(String[] args) {
      System.out.println("Start of main.");
      Singleton.getInstance().doSomething();
      System.out.println("End of main.");
   }
}

The output shows that the main method starts before getInstance() is called, thus lazily loaded.

Start of main.
In getInstance().
In SingletonHolder static block.
In constructor.
Singleton working.
End of main.

Eager loading without a holder

public class Singleton {

   static {
      System.out.println("In Singleton static block.");
   }

   private static final Singleton INSTANCE = new Singleton();

   public static Singleton getInstance() {
      System.out.println("In getInstance().");
      return INSTANCE;
   }

   private Singleton() {
      System.out.println("In constructor.");
   }

   private void doSomething() {
      System.out.println("Singleton working.");
   }

   public static void main(String[] args) {
      System.out.println("Start of main.");
      Singleton.getInstance().doSomething();
      System.out.println("End of main.");
   }

}

The output shows that the main method starts after the getInstance() method is called, thus eagerly loaded.

In Singleton static block.
In constructor.
Start of main.
In getInstance().
Singleton working.
End of main.

The answer of Robert is incorrect since it has a main method in the Singleton class.

The Bill Pugh Singleton does offer lazy loading in java, but the container is not responsible for this (only when you have other static methods or members on your class). People claim it (the container) does, but in general it does not.

The benefit it offers is that it offers a way of handling construction exceptions in the getInstance method. While the one without container does not (you have to handle it outside of the class).

Example code to prove lazy loading is offered by the Pugh Singleton, but the Container is not responsible for this

public class MyClass
{
    public static void main(String[] args) {
        System.out.println(BillPughSingleton.getInstance());
        System.out.println(BillPughSingletonWithoutContainer.getInstance());
    }
}

class BillPughSingletonWithoutContainer
{
    private static BillPughSingletonWithoutContainer instance = new BillPughSingletonWithoutContainer();

    static {
        System.out.println("Static is now loaded in BillPughSingletonWithoutContainer");
    }

    private BillPughSingletonWithoutContainer() {}

    public static BillPughSingletonWithoutContainer getInstance()
    {
        return instance;
    }
}

class BillPughSingleton
{
    private static class Container
    {
        public static BillPughSingleton instance = new BillPughSingleton();
    }

    private BillPughSingleton() {}

    public static BillPughSingleton getInstance()
    {
        return Container.instance;
    }
}

Output:

BillPughSingleton@4c762604
Static is now loaded in BillPughSingletonWithoutContainer
BillPughSingletonWithoutContainer@727803de

Now we expect the output should be:

Static is now loaded in BillPughSingletonWithoutContainer
BillPughSingleton@4c762604
BillPughSingletonWithoutContainer@727803de

But the one without container gets created once we access the following code:

System.out.println(BillPughSingletonWithoutContainer.getInstance());

So it is created lazily, when we are about to access the getInstance method. It is not created on import, not created on referring, but created on accessing the method. Which proves that the Pugh Singleton Container does not offer lazy loading in java.

So does the Pugh Singleton have (other) benefits?

Besides it being thread safe, it allows Exceptions to be handled in the getInstance method:

class BillPughSingleton
{
    private static class Container
    {
        public static BillPughSingleton instance = new BillPughSingleton();
    }

    private BillPughSingleton() {
        throw new RuntimeException();
    }

    public static BillPughSingleton getInstance()
    {
        BillPughSingleton singleton = null;
        try {
            singleton = Container.instance;
        } catch(ExceptionInInitializerError e) {
            System.out.println(e.getMessage());
        }
        return singleton;
    }
}

The only problem with this is that we have to catch a ExceptionInInitializerError-exception, and normally we would not catch any Error-exceptions.

Note; the Pugh singleton Container does offer lazy loading only when you have other public static methods/members on your singleton (like Roberts example)

If you have other static method on your singleton, the instance will be created when accessing those static members. But the question is when a Singleton has this. Normally getInstance is the only public static member of the class.

Note: there is nothing wrong with using the container...

But to claim it offers lazy loading, it not true. Only when you have more public static members on your class, then it offers lazy loading.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM