Singleton: 6 Ways To Write and Use in Java Programming

In Java programming, object creation or instantiation of a class is done with "new" operator and with a public constructor declared in the class as below.

Java
 
Clazz clazz = new Clazz();


We can read the code snippet as follows:

Clazz() is the default public constructor called with "new" operator to create or instantiate an object for Clazz class and assigned to variable clazz, whose type is Clazz.

While creating a singleton, we have to ensure only one single object is created or only one instantiation of a class takes place. To ensure this, the following common things become the prerequisite.

  1. All constructors need to be declared as "private" constructors. 
    • It prevents the creation of objects with "new" operator outside the class.
  2. A private constant/variable object holder to hold the singleton object is needed; i.e., a private static or a private static final class variable needs to be declared.
    • It holds the singleton object. It acts as a single source of reference for the singleton object
    • By convention, the variable is named as INSTANCE or instance.
  3. A static method to allow access to the singleton object by other objects is required.
    • This static method is also called a static factory method, as it controls the creation of objects for the class.
    • By convention, the method is named as getInstance()

With this understanding, let us delve deeper into understanding singleton. Following are the 6 ways one can create a singleton object for a class.

1. Static Eager Singleton Class

When we have all the instance properties in hand, and we like to have only one object and a class to provide a structure and behavior for a group of properties related to each other, we can use the static eager singleton class. This is well-suited for application configuration and application properties.

Java
 
public class EagerSingleton {
  
  private static final EagerSingleton INSTANCE = new EagerSingleton();

  private EagerSingleton() {}
      
  public static EagerSingleton getInstance() {
    return INSTANCE;
  }

  public static void main(String[] args) {
    EagerSingleton eagerSingleton = EagerSingleton.getInstance();
  }
}


The singleton object is created while loading the class itself in JVM and assigned to the INSTANCE constant. getInstance() provides access to this constant.

While compile-time dependencies over properties are good, sometimes run-time dependencies are required. In such a case, we can make use of a static block to instantiate singleton.

Java
 
public class EagerSingleton {

    private static EagerSingleton instance;

    private EagerSingleton(){}

 // static block executed during Class loading
    static {
        try {
            instance = new EagerSingleton();
        } catch (Exception e) {
            throw new RuntimeException("Exception occurred in creating EagerSingleton instance");
        }
    }

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


The singleton object is created while loading the class itself in JVM as all static blocks are executed while loading. Access to the instance variable is provided by the getInstance() static method.

2. Dynamic Lazy Singleton Class

Singleton is more suited for application configuration and application properties. Consider heterogenous container creation, object pool creation, layer creation, facade creation, flyweight object creation, context preparation per requests, and sessions, etc.: they all require dynamic construction of a singleton object for better "separation of concern." In such cases, dynamic lazy singletons are required.

Java
 
public class LazySingleton {

    private static LazySingleton instance;

    private LazySingleton(){}

    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

 

The singleton object is created only when the getInstance() method is called. Unlike the static eager singleton class, this class is not thread-safe.

Java
 
public class LazySingleton {

    private static LazySingleton instance;

    private LazySingleton(){}

    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }

}


The getInstance() method needs to be synchronized to ensure the getInstance() method is thread-safe in singleton object instantiation.

3. Dynamic Lazy Improved Singleton Class

Java
 
public class LazySingleton {

    private static LazySingleton instance;

    private LazySingleton(){}

    public static LazySingleton getInstance() {
      if (instance == null) {
        synchronized (LazySingleton.class) {
            if (instance == null) {
                instance = new LazySingleton();
            }
        }
      }
      return instance;
    }

}


Instead of locking the entire getInstance() method, we could lock only the block with double-checking or double-checked locking to improve performance and thread contention.

Java
 
public class EagerAndLazySingleton {

    private EagerAndLazySingleton(){}

    private static class SingletonHelper {
        private static final EagerAndLazySingleton INSTANCE = new EagerAndLazySingleton();
    }

    public static EagerAndLazySingleton getInstance() {
        return SingletonHelper.INSTANCE;
    }
}


The singleton object is created only when the getInstance() method is called. It is a Java memory-safe singleton class. It is a thread-safe singleton and is lazily loaded. It is the most widely used and recommended.

Despite performance and safety improvement, the only objective to create just one object for a class is challenged by memory reference, reflection, and serialization in Java.

All of these affect both static and dynamic singletons. In order to overcome such challenges, it requires us to declare the instance holder as volatile and override equals(), hashCode() and readResolve() of default parent class of all classes in Java, Object.class.

4. Singleton With Enum

The issue with memory safety, reflection, and serialization can be avoided if enums are used for static eager singleton. 

Java
 
public enum EnumSingleton {
    INSTANCE;
}


These are static eager singletons in disguise, thread safe. It is good to prefer an enum where a static eagerly initialized singleton is required.

5. Singleton With Function and Libraries

While understanding the challenges and caveats in singleton is a must to appreciate, why should one worry about reflection, serialization, thread safety, and memory safety when one can leverage proven libraries? Guava is such a popular and proven library, handling a lot of best practices for writing effective Java programs.

I have had the privilege of using the Guava library to explain supplier-based singleton object instantiation to avoid a lot of heavy-lifting lines of code. Passing a function as an argument is the key feature of functional programming. While the supplier function provides a way to instantiate object producers, in our case, the producer must produce only one object and should keep returning the same object repeatedly after a single instantiation. We can memoize/cache the created object. Functions defined with lambdas are usually lazily invoked to instantiate objects and the memoization technique helps in lazily invoked dynamic singleton object creation.

Java
 
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;

public class SupplierSingleton {
    private SupplierSingleton() {}

    private static final Supplier<SupplierSingleton> singletonSupplier = Suppliers.memoize(()-> new SupplierSingleton());

    public static SupplierSingleton getInstance() {
        return singletonSupplier.get();
    }

    public static void main(String[] args) {
        SupplierSingleton supplierSingleton = SupplierSingleton.getInstance();
    }
}


Functional programming, supplier function, and memoization help in the preparation of singletons with a cache mechanism. This is most useful when we don't want heavy framework deployment.

6. Singleton With Framework: Spring, Guice

Why worry about even preparing an object via supplier and maintaining cache? Frameworks like Spring and Guice work on POJO objects to provide and maintain singleton.

This is heavily used in enterprise development where many modules each require their own context with many layers. Each context and each layer are good candidates for singleton patterns.

Java
 
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

class SingletonBean { }

@Configuration
public class SingletonBeanConfig {

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
    public SingletonBean singletonBean() {
        return new SingletonBean();
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SingletonBean.class);
        SingletonBean singletonBean = applicationContext.getBean(SingletonBean.class);
    }
}


Spring is a very popular framework. Context and Dependency Injection are the core of Spring.

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;

interface ISingletonBean {}

class SingletonBean implements  ISingletonBean { }

public class SingletonBeanConfig extends AbstractModule {

    @Override
    protected void configure() {
        bind(ISingletonBean.class).to(SingletonBean.class);
    }

    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new SingletonBeanConfig());
        SingletonBean singletonBean = injector.getInstance(SingletonBean.class);
    }
}


Guice from Google is also a framework to prepare singleton objects and an alternative to Spring.

Following are the ways singleton objects are leveraged with "Factory of Singletons."

  1. Prototype or Flyweight
  2. Object pools
  3. Facades
  4. Layering
  5. Context and class loaders
  6. Cache
  7. Cross-cutting concerns and aspect-oriented programming

Conclusion

Patterns appear when we solve use cases for our business problems and for our non-functional requirement constraints like performance, security, and CPU and memory constraints. Singleton objects for a given class is such a pattern, and requirements for its use will fall in place to discover. The class by nature is a blueprint to create multiple objects, yet the need for dynamic heterogenous containers to prepare "context," "layer,", "object pools," and "strategic functional objects" did push us to make use of declaring globally accessible or contextually accessible objects. 

Thanks for your valuable time, and I hope you found something useful to revisit and discover.

 

 

 

 

Top