Why the Singleton Pattern Matters: Lazy Loading, Thread Safety, and Real‑World Java Examples
This article explains the Singleton design pattern in Java, illustrates why a single instance is essential for configuration classes, compares eager and lazy initialization, shows thread‑safe implementations, and demonstrates extensions that limit the number of instances with practical code samples.
Purpose of the Singleton Pattern
When many parts of an application need to read the same configuration (e.g., an AppConfig class that parses XML or properties files), creating a separate instance for each caller wastes memory and CPU. The Singleton pattern guarantees that only one instance of a class exists within a JVM, eliminating redundant construction and reducing garbage‑collection pressure.
Eager (static) Singleton
public class Singleton {
private Singleton() {
System.out.println("Singleton is create");
}
private static final Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}The instance is created when the class is loaded. This approach is simple and thread‑safe, but it does not support lazy loading—if the singleton is never used, the object is still allocated.
Lazy‑Loading Singleton with Synchronized Access
public class LazySingleton {
private LazySingleton() {
System.out.println("LazySingleton is create");
}
private static LazySingleton instance = null;
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
public static void createString() {
System.out.println("create String");
}
public static void main(String[] args) {
LazySingleton.createString();
}
}The getInstance method creates the object on first call, avoiding the eager‑load cost. The method is synchronized to prevent multiple threads from creating separate instances simultaneously.
Performance impact of synchronization
A benchmark creates several threads, each invoking getInstance thousands of times and records the elapsed time. The synchronized lazy version shows a noticeable slowdown compared with the eager version because each call incurs lock acquisition and release.
Static Inner‑Class (Holder) Technique
public class StaticSingleton {
private StaticSingleton() {
System.out.println("StaticSingleton is create");
}
private static class SingletonHolder {
private static final StaticSingleton INSTANCE = new StaticSingleton();
}
public static StaticSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}The inner class SingletonHolder is not loaded until getInstance is invoked. JVM class‑loading guarantees that the static field is initialized exactly once, providing lazy initialization without any explicit synchronization and thus full thread safety.
Limiting the Number of Instances (Fixed‑Size Pool)
import java.util.HashMap;
import java.util.Map;
public class ThreeSingleton {
private static final String PREFIX = "cache";
private static final int MAX = 3;
private static final Map<String, ThreeSingleton> POOL = new HashMap<>();
private static int counter = 1;
private ThreeSingleton() { }
public static synchronized ThreeSingleton getInstance() {
String key = PREFIX + counter;
ThreeSingleton obj = POOL.get(key);
if (obj == null) {
obj = new ThreeSingleton();
POOL.put(key, obj);
}
counter = (counter % MAX) + 1; // cycle 1..MAX
return obj;
}
public static void main(String[] args) {
ThreeSingleton t1 = getInstance();
ThreeSingleton t2 = getInstance();
ThreeSingleton t3 = getInstance();
ThreeSingleton t4 = getInstance();
ThreeSingleton t5 = getInstance();
ThreeSingleton t6 = getInstance();
System.out.println(t1);
System.out.println(t2);
System.out.println(t3);
System.out.println(t4);
System.out.println(t5);
System.out.println(t6);
}
}The map acts as a cache. The method cycles through keys cache1, cache2, cache3. After three instances are created, subsequent calls return the previously stored objects, so only three distinct instances ever exist.
Choosing an Implementation
Eager singleton : simplest, thread‑safe, suitable when the instance is cheap to create and will definitely be used.
Lazy synchronized singleton : defers creation but incurs lock overhead on every access.
Holder‑class singleton : combines lazy initialization with zero synchronization cost; recommended for most cases.
Fixed‑size pool : useful when a small, bounded number of instances is required (e.g., connection pools, resource caches).
All implementations rely on a private constructor to prevent external instantiation and expose a static accessor that returns the managed instance(s).
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Art of Distributed System Architecture Design
Introductions to large-scale distributed system architectures; insights and knowledge sharing on large-scale internet system architecture; front-end web architecture overviews; practical tips and experiences with PHP, JavaScript, Erlang, C/C++ and other languages in large-scale internet system development.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
