Singleton: 6 Ways To Write and Use in Java Programming
Here, learn how and when to write singletons and gain an appreciation of the nuances and caveats in creating singleton objects.
Join the DZone community and get the full member experience.
Join For FreeIn 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.
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 forClazz
class and assigned to variableclazz
, whose type isClazz
.
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.
- All constructors need to be declared as "
private
" constructors.- It prevents the creation of objects with "
new
" operator outside the class.
- It prevents the creation of objects with "
- 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
orinstance
.
- 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.
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.
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.
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.
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
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.
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.
- Memory reference: In a multithreaded environment, reordering of read and writes for threads can occur on a referenced variable, and a dirty object read can happen anytime if the variable is not declared volatile.
- Reflection: With reflection, the private constructor can be made public and a new instance can be created.
- Serialization: A serialized instance object can be used to create another instance of the same class.
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.
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.
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.
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."
- Factory Method, Abstract Factory, and Builders are associated with the creation and construction of specific objects in JVM. Wherever we envision the construction of an object with specific needs, we can discover the singleton's need. Further places where one can check out and discover singleton are as follows.
- Prototype or Flyweight
- Object pools
- Facades
- Layering
- Context and class loaders
- Cache
- 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.
Opinions expressed by DZone contributors are their own.
Comments