What is a singleton in Java?
Singleton is one of the simplest class-level design patterns. Sometimes people say "this class is singleton”, which means that the class implements the singleton design pattern. Sometimes it is necessary to write a class where we restrict instantiation to a single object. For example, a class responsible for logging or connecting to a database. The singleton design pattern describes how we can achieve this. A singleton is a design pattern that does two things:It guarantees that there will only ever be one instance of the class.
It provides a single point of global access to that instance.
A private constructor. This limits the ability to create objects of the class outside of the class itself.
A public static method that returns the instance of the class. This method is called getInstance. This is the point of global access to the class instance.

Implementation options
The singleton design pattern is applied in various ways. Each option is good and bad in its own way. As always, there is no perfect option here, but we should strive for one. First of all, let's decide what constitutes good and bad, and what metrics affect how we assess the various implementations of the design pattern. Let's start with the good. Here are factors that make an implementation more juicy and appealing:Lazy initialization: the instance is not created until it is needed.
Simple and transparent code: this metric, of course, is subjective, but it is important.
Thread safety: correct operation in a multi-threaded environment.
High performance in a multi-threaded environment: little or no thread blocking when sharing a resource.
No lazy initialization: when the class is loaded when the application starts, regardless of whether or not it is needed (paradoxically, in the IT world it is better to be lazy)
Complex and difficult-to-read code. This metric is also subjective. If your eyes start bleeding, we'll assume the implementation isn't the best.
Lack of thread safety. In other words, "thread danger". Incorrect operation in a multi-threaded environment.
Poor performance in a multi-threaded environment: threads block each other all the time or often when sharing a resource.
Code
Now we're ready to consider various implementation options and indicate the pros and cons:Simple
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return INSTANCE;
}
}
The simplest implementation.
Pros:
Simple and transparent code
Thread safety
High performance in a multi-threaded environment
- No lazy initialization.
Lazy initialization
public class Singleton {
private static final Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Pros:
Lazy initialization.
Not thread safe
Synchronized access
public class Singleton {
private static final Singleton INSTANCE;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Pros:
Lazy initialization.
Thread safety
Poor multithreaded performance
Double-checked locking
public class Singleton {
private static final Singleton INSTANCE;
private Singleton() {
}
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
Pros:
Lazy initialization.
Thread safety
High performance in a multi-threaded environment
Not supported in earlier versions of Java below 1.5 (the usage of volatile keyword is fixed since the 1.5 version)
Class holder
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
public static final Singleton HOLDER_INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.HOLDER_INSTANCE;
}
}
Pros:
Lazy initialization.
Thread safety.
High performance in a multi-threaded environment.
Correct operation requires a guarantee that the singleton object is initialized without errors. Otherwise, the first call to the getInstance method will result in an ExceptionInInitializerError, and all subsequent calls will produce a NoClassDefFoundError.
Implementation | Lazy initialization | Thread safety | Multithreaded performance | When to use? |
---|---|---|---|---|
Simple | - | + | Fast | Never. Or possibly when lazy initialization is not important. But never would be better. |
Lazy initialization | + | - | Not applicable | Always when multithreading is not needed |
Synchronized access | + | + | Slow | Never. Or possibly when multithreaded performance does not matter. But never would be better. |
Double-checked locking | + | + | Fast | In rare cases when you need to handle exceptions when creating the singleton (when the class holder singleton is not applicable) |
Class holder | + | + | Fast | Whenever multithreading is needed and there is a guarantee that the singleton object will be created without problems. |
Eager Initialization
In Eager Initialization, the Singleton instance is created as soon as the class is loaded. This means the instance is ready to use even before it's needed. It’s like making a cup of coffee in the morning just in case you might want it later. No waiting—just grab and go!
Syntax of Eager Initialization
Let’s look at how this works in Java:
public class EagerSingleton {
// Instance is created when the class is loaded
private static final EagerSingleton INSTANCE = new EagerSingleton();
// Private constructor prevents instantiation from other classes
private EagerSingleton() {
System.out.println("EagerSingleton instance created!");
}
// Public method to provide access to the instance
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
Usage Example:
public class Main {
public static void main(String[] args) {
// Accessing the Singleton instance
EagerSingleton instance1 = EagerSingleton.getInstance();
EagerSingleton instance2 = EagerSingleton.getInstance();
System.out.println(instance1 == instance2); // Output: true
}
}
Output:
EagerSingleton instance created!
true
Notice that the message "EagerSingleton instance created!" is printed only once. Both instance1
and instance2
point to the same object. That’s the Singleton magic!
Pros and Cons of Eager Initialization
Now that you’ve seen how easy it is to implement, let’s weigh the pros and cons of eager initialization.
Advantages:
- Simplicity: It’s super easy to implement. No complex synchronization or multi-threading concerns.
- Thread Safety: Since the instance is created during class loading, it’s inherently thread-safe without needing extra code.
- Reliable Access: The instance is always available when you need it. No null checks required!
Disadvantages:
- Resource Waste: If the instance is never used, the memory and resources allocated for it are wasted.
- Lack of Flexibility: You can’t delay the creation of the instance, even if it’s expensive to create.
So, when should you use eager initialization? If the Singleton object is lightweight and will definitely be used, this method is perfect. Otherwise, consider lazy initialization for better resource management.
Eager vs. Lazy Initialization
Here’s a quick side-by-side comparison to help you decide when to use eager or lazy initialization:
Feature | Eager Initialization | Lazy Initialization |
---|---|---|
Instance Creation | At class loading | When needed |
Thread Safety | Inherently thread-safe | Requires synchronization |
Resource Efficiency | May waste resources if unused | Efficient, only created when necessary |
Complexity | Simple and straightforward | More complex, requires careful handling |
Pros and cons of the singleton pattern
In general, a singleton does exactly what is expected of it:It guarantees that there will only ever be one instance of the class.
It provides a single point of global access to that instance.
A singleton violates the single responsibility principle: in addition to its direct duties, the singleton class also controls the number of instances.
An ordinary class's dependence on a singleton is not visible in the class's public contract.
Global variables are bad. Ultimately, a singleton turns into a hefty global variable.
The presence of a singleton reduces the testability of the application as a whole and the classes that use the singleton in particular.
So far, you've nailed the basics of the Singleton pattern. But wait—did you know that sneaky tricks like reflection and serialization can actually break your Singleton?
Don’t worry! I’m here to show you exactly how these issues happen and, more importantly, how to stop them. Let’s dive in!
How Reflection Can Break Singleton (and How to Prevent It)
Reflection in Java allows you to inspect and modify classes at runtime. Sounds cool, right? But it can be dangerous for Singleton. Here’s why:
Even if you make your constructor private
, reflection can still access it and create multiple instances.
Reflection in Action
import java.lang.reflect.Constructor;
public class ReflectionBreaksSingleton {
public static void main(String[] args) {
try {
Singleton instance1 = Singleton.getInstance();
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true); // Bypassing private constructor
Singleton instance2 = constructor.newInstance();
System.out.println(instance1 == instance2); // Output: false
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
System.out.println("Singleton instance created!");
}
public static Singleton getInstance() {
return INSTANCE;
}
}
Oops! Reflection just created a second instance! Not good.
Preventing Reflection Attacks
One simple trick is to throw an exception if someone tries to create another instance. Let’s fix it:
class Singleton {
private static final Singleton INSTANCE = new Singleton();
private static boolean instanceCreated = false;
private Singleton() {
if (instanceCreated) {
throw new RuntimeException("Use getInstance() to create the Singleton!");
}
instanceCreated = true;
}
public static Singleton getInstance() {
return INSTANCE;
}
}
Now, even reflection can’t sneak past this guard!
How Serialization Can Break Singleton (and How to Fix It)
Serialization lets us save and restore objects. But here’s the catch: deserialization creates a new object, breaking the Singleton rule.
Serialization Problem Example
import java.io.*;
public class SerializationBreaksSingleton {
public static void main(String[] args) throws Exception {
Singleton instance1 = Singleton.getInstance();
// Serialize
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
oos.writeObject(instance1);
oos.close();
// Deserialize
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.ser"));
Singleton instance2 = (Singleton) ois.readObject();
ois.close();
System.out.println(instance1 == instance2); // Output: false
}
}
class Singleton implements Serializable {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
Yikes! Serialization just broke our Singleton. Let’s fix that.
Fixing with readResolve()
By adding a readResolve()
method, we can ensure deserialization returns the same instance.
class Singleton implements Serializable {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
// Fix for serialization
private Object readResolve() throws ObjectStreamException {
return INSTANCE;
}
}
Problem solved! Now deserialization will always return the original Singleton instance.
Reflection + Serialization: A Double Threat!
Reflection and serialization are like the dynamic duo of destruction when it comes to breaking Singleton. But don’t worry—we can outsmart them.
The Ultimate Protection: Enum Singleton
Want a bulletproof Singleton? Use an enum. It’s immune to both reflection and serialization!
enum Singleton {
INSTANCE;
public void showMessage() {
System.out.println("Hello from Enum Singleton!");
}
}
Usage:
public class Main {
public static void main(String[] args) {
Singleton singleton = Singleton.INSTANCE;
singleton.showMessage();
}
}
Why is this awesome?
- **Safe from Reflection:** Java prevents reflection from creating enum instances.
- **Safe from Serialization:** Enums handle serialization internally. No
readResolve()
needed. - **Simple and Clean:** Less code, fewer problems.
GO TO FULL VERSION