— Java, Singleton Design Pattern — 2 min read
Singletons are a design pattern used in software development to ensure that a class has only one instance and provides a global point of access to it. In Java, creating singletons is straightforward and can be accomplished using various techniques. In this article, we will explore some of the common approaches to creating singletons in Java, along with examples.
The eager initialization approach involves creating an instance of the singleton class at the time of class loading. Here's an example of a singleton class created using eager initialization:
1public class EagerSingleton {2 private static final EagerSingleton INSTANCE = new EagerSingleton();3
4 private EagerSingleton() {5 // Private constructor to prevent instantiation6 }7
8 public static EagerSingleton getInstance() {9 return INSTANCE;10 }11
12 // Other methods and fields13}
In the above example, the INSTANCE
variable is declared as final
and static
, ensuring that it is a class-level variable accessible without creating an instance of the class. The constructor is marked as private, preventing external classes from instantiating the singleton. The getInstance()
method provides a global point of access to the singleton instance.
Lazy initialization defers the creation of the singleton instance until it is actually needed. This approach is useful when the singleton is resource-intensive or requires complex initialization. Here's an example of a singleton class created using lazy initialization:
1public class LazySingleton {2 private static LazySingleton INSTANCE;3
4 private LazySingleton() {5 // Private constructor to prevent instantiation6 }7
8 public static synchronized LazySingleton getInstance() {9 if (INSTANCE == null) {10 INSTANCE = new LazySingleton();11 }12 return INSTANCE;13 }14
15 // Other methods and fields16}
In the above example, the getInstance()
method checks if the singleton instance has been created. If not, it creates a new instance. The method is marked as synchronized
to ensure thread safety in a multi-threaded environment. While this approach provides lazy initialization, it introduces a slight performance overhead due to the synchronized keyword.
Double-checked locking is an optimization technique to reduce the overhead of acquiring locks. It performs a quick check of the singleton instance without acquiring the lock if the instance has already been created. Here's an example of a singleton class created using double-checked locking:
1public class DoubleCheckedSingleton {2 private static volatile DoubleCheckedSingleton INSTANCE;3
4 private DoubleCheckedSingleton() {5 // Private constructor to prevent instantiation6 }7
8 public static DoubleCheckedSingleton getInstance() {9 if (INSTANCE == null) {10 synchronized (DoubleCheckedSingleton.class) {11 if (INSTANCE == null) {12 INSTANCE = new DoubleCheckedSingleton();13 }14 }15 }16 return INSTANCE;17 }18
19 // Other methods and fields20}
In the above example, the volatile
keyword ensures that changes made to the INSTANCE
variable are immediately visible to other threads. The double-checked locking is performed within a synchronized block to guarantee thread safety. This approach provides both lazy initialization and improved performance in multi-threaded scenarios.
Java enums provide a concise and thread-safe way to create singletons. Enum instances are globally accessible and have implicit serialization support. Here's an example of a singleton created using an enum:
1public enum EnumSingleton {2 INSTANCE;3
4 // Other methods and fields5}
In the above example, the singleton instance is created implicitly when the enum value INSTANCE
is accessed. Enum singletons are thread-safe by default and can be easily serialized without losing the singleton property.
Singletons play a crucial role in software design by ensuring that only one instance of a class exists throughout the application. In this article, we explored different approaches to creating singletons in Java, including eager initialization, lazy initialization, double-checked locking, and enum singletons. Each approach has its pros and cons, and the choice depends on specific requirements and use cases.