Double-Checked Locking and volatile in Java
Fast recall
Quick revision
Core answer
Deep Java knowledge — instruction reordering and the need for volatile in the double-checked locking pattern
Implementing DCL without volatile — broken in Java before 1.5 and logically still wrong without it today
Explain instruction reordering and what a partially constructed object means at the JVM level
Interview-ready guide
Interview answer
Double-checked locking is a pattern for lazy singleton initialization that's thread-safe but avoids synchronizing every access.
The idea is you check if the instance is null before taking a lock. If it's already created, you return it immediately without any synchronization overhead. Only if it's null do you synchronize, and then you check again inside the synchronized block to make sure another thread didn't create it while you were waiting for the lock.
That's why it's called double-checked. Now here's the critical part: you absolutely need volatile on the instance field. Without it, the pattern is broken due to something called instruction reordering.
The JVM is allowed to reorder instructions for performance as long as it doesn't change single-threaded behavior. So when you write new DatabaseConnectionPool, the JVM might set the instance reference before the constructor finishes running. Another thread could then see a non-null instance, skip the synchronization, and start using the object before its fields are initialized.
You'd access a connections list that's still null even though the object exists. That's what we call a partially constructed object, and it's a nightmare to debug. Volatile prevents this by enforcing a happens-before relationship.
When you write to a volatile variable, everything before that write is guaranteed to be visible to any thread that subsequently reads that variable. So the instance reference can't be published until the constructor completes. That's why volatile is mandatory for correct double-checked locking.
Double-Checked Locking and volatile in Java
Direct Answer
Double-checked locking is a pattern for thread-safe lazy singleton initialization. Without volatile on the instance field, it's broken — the JVM may allow the instance to be published in a partially initialized state due to instruction reordering. Volatile prevents this by establishing a happens-before relationship.
Code Example
public class DatabaseConnectionPool {
private volatile static DatabaseConnectionPool instance; // volatile is critical
private final List<Connection> connections;
private DatabaseConnectionPool() {
// expensive initialization
connections = new ArrayList<>();
for (int i = 0; i < 10; i++) {
connections.add(createConnection());
}
}
public static DatabaseConnectionPool getInstance() {
if (instance == null) { // first check without locking
synchronized (DatabaseConnectionPool.class) {
if (instance == null) { // second check with lock
instance = new DatabaseConnectionPool(); // volatile write
}
}
}
return instance; // volatile read ensures fully constructed
}
private Connection createConnection() { return null; }
}
Common Mistakes & Pitfalls
- Forgetting volatile: This is the critical mistake. Without volatile, the pattern is broken — the instance can be published before it's fully initialized, causing other threads to see partially constructed objects with null or default field values. This bug is intermittent and extremely hard to reproduce in testing.
- Double-checking with the wrong lock: Synchronizing on the wrong object or using instance as the lock (before it's created) leads to race conditions. You must synchronize on the class object or a dedicated static lock.
- Not understanding the second check: Freshers often wonder why you check twice. The second check inside the synchronized block is necessary because multiple threads could have passed the first check before the instance was created, and without the second check they'd all create their own instance.
Check your understanding
Answer these without looking back at the article.
- What is instruction reordering in the JVM?
- Is there a simpler way to implement a singleton in Java?
- What is the enum singleton pattern?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.