Approaches to implementing Singleton pattern in Java
Singleton is a creation design pattern to provide one and only instance of an object. In simple terms to achieve a singleton we need to (1) make the constructors of the class private, (2) create and store the object privately and (3) provide access to the created instance through a public method. There are several approaches to implement thread safe singleton pattern in Java. In this article we will explore some these approaches and analyze.Lazy Initialization
In Lazy Initialization approach we use the double check locking mechanism.
- First we check if the instance is already initialized. In that case we return the object immediately without any locking.
- If the object is not initialized we obtain the lock.
- We again check if the instance is initialized. This is necessary if another thread which obtained the lock first has already done the initialization. If initialized return the object.
- Otherwise, create and return the object.
It is also necessary that while storing the instance we need to associate the "volatile" keyword. The volatile keyword ensures that created instance is always written and read from RAM and not any local registers to prevent any issues when interleaving of threads.
The example below shows a sample implementation with Lazy Initialization approach.
public class LazyInitTest { // Store the object privately private static volatile LazyInitTest instance = null; // Make constructor private private LazyInitTest() { System.out.println("In LazyInitTest ..."); } // Provide a public function to access the object public static LazyInitTest getInstance() { if ( instance == null ) { synchronized(LazyInitTest.class) { if ( instance == null ) { instance = new LazyInitTest(); } } } return instance; } // Test code public static void main(String[] args) { // First thread Thread t1 = new Thread(new Runnable() { @Override public void run() { LazyInitTest obj = LazyInitTest.getInstance(); } }); t1.start(); // Second thread Thread t2 = new Thread(new Runnable() { @Override public void run() { LazyInitTest obj = LazyInitTest.getInstance(); } }); t2.start(); } }Output:-
In LazyInitTest ...The Lazy Initialization approach creates the object only when it is actually needed. For large objects this would be an advantage. We have the overhead of concurrency checks in this approach.
Eager Initialization
In the Eager Initialization approach we always create the object. The object is created much before it is actually needed or used.
The example below shows a sample implementation with Eager Initialization approach.
public class EagerInit { // Store the object privately private static final EagerInit instance = new EagerInit(); // Make constructor private private EagerInit() { System.out.println("In EagerInitTest ..."); } // Provide a public function to access the object public static EagerInit getInstance() { return instance; } // Test code public static void main(String[] args) { // First thread Thread t1 = new Thread(new Runnable() { @Override public void run() { EagerInit obj = EagerInit.getInstance(); } }); t1.start(); // Second thread Thread t2 = new Thread(new Runnable() { @Override public void run() { EagerInit obj = EagerInit.getInstance(); } }); t2.start(); } }Output:-
In EagerInitTest ...The Eager Initialization approach creates the object much before it is actually needed. For large objects this would be an disadvantage. We don't have the cost of concurrency checks in this approach.
Enum approach
The most simplest approach to implementing Singleton's is the enum approach. Leverages the guarantee from Java that an enum value is initialized only once and is thread safe. The example below shows a sample implementation with Enum approach.public enum EnumTest { INSTANCE; // Test code public static void main(String[] args) { // First thread Thread t1 = new Thread(new Runnable() { @Override public void run() { EnumTest obj = EnumTest.INSTANCE; } }); t1.start(); // Second thread Thread t2 = new Thread(new Runnable() { @Override public void run() { EnumTest obj = EnumTest.INSTANCE; } }); t2.start(); } }
Conclusion
The recommended approach to implementing Singleton design pattern in Java is using the Enum approach. The Enum approach is very simple and thread safe implicitly. If a class implements Serializable interface multiple objects would get created when we do readObject during de-serialization. The Enum approach has no drawbacks regarding serializable objects.
0 comments:
Post a Comment