From 4b6a02f39676b2976a1f36d4cc8f2af60f9eca75 Mon Sep 17 00:00:00 2001 From: Ankur Date: Tue, 23 Jun 2026 11:11:02 +0530 Subject: [PATCH] Sync Singleton to Java 25: split 6 implementations into separate files, consolidated Main.java demo --- 01-creational/singleton/AppConfig.java | 19 ---------- 01-creational/singleton/AppConfigDCL.java | 31 ++++++++++++++++ 01-creational/singleton/AppConfigEager.java | 24 +++++++++++++ 01-creational/singleton/AppConfigEnum.java | 19 ++++++++++ 01-creational/singleton/AppConfigHolder.java | 19 ++++++++++ .../singleton/AppConfigNaiveLazy.java | 16 +++++++++ .../singleton/AppConfigSynchronized.java | 16 +++++++++ 01-creational/singleton/Main.java | 35 ++++++++++++++++--- README.md | 20 +++++++++++ 9 files changed, 176 insertions(+), 23 deletions(-) delete mode 100644 01-creational/singleton/AppConfig.java create mode 100644 01-creational/singleton/AppConfigDCL.java create mode 100644 01-creational/singleton/AppConfigEager.java create mode 100644 01-creational/singleton/AppConfigEnum.java create mode 100644 01-creational/singleton/AppConfigHolder.java create mode 100644 01-creational/singleton/AppConfigNaiveLazy.java create mode 100644 01-creational/singleton/AppConfigSynchronized.java diff --git a/01-creational/singleton/AppConfig.java b/01-creational/singleton/AppConfig.java deleted file mode 100644 index db2f02d..0000000 --- a/01-creational/singleton/AppConfig.java +++ /dev/null @@ -1,19 +0,0 @@ -package singleton; -public class AppConfig { - - private AppConfig() { - System.out.println("Reading configuration from file..."); - } - - private static class Holder { - static final AppConfig INSTANCE = new AppConfig(); - } - - public static AppConfig getInstance() { - return Holder.INSTANCE; - } - - public String getProperty(String key) { - return System.getProperty(key, "(not set)"); - } -} diff --git a/01-creational/singleton/AppConfigDCL.java b/01-creational/singleton/AppConfigDCL.java new file mode 100644 index 0000000..6772ab0 --- /dev/null +++ b/01-creational/singleton/AppConfigDCL.java @@ -0,0 +1,31 @@ +package singleton; + +public class AppConfigDCL { + + // volatile is MANDATORY for DCL to work correctly. + // Without it, the JVM can reorder the writes inside the constructor + // and return a partially-constructed object to a second thread. + private static volatile AppConfigDCL instance; + + private AppConfigDCL() { /* load config */ } + + public static AppConfigDCL getInstance() { + + // First check: no lock, instant return for the 99.99% case + // (instance already created, just return it) + if (instance == null) { + + // Lock only when first check says we might need to create + synchronized (AppConfigDCL.class) { + + // Second check: re-verify under the lock. + // Another thread might have created the instance between + // our first check and acquiring the lock. + if (instance == null) { + instance = new AppConfigDCL(); + } + } + } + return instance; + } +} diff --git a/01-creational/singleton/AppConfigEager.java b/01-creational/singleton/AppConfigEager.java new file mode 100644 index 0000000..0bcb6e1 --- /dev/null +++ b/01-creational/singleton/AppConfigEager.java @@ -0,0 +1,24 @@ +package singleton; + +public class AppConfigEager { + + // The JVM creates this instance when the class is first loaded. + // static guarantees it belongs to the class, not any instance. + // final guarantees it can never be reassigned. + private static final AppConfigEager INSTANCE = new AppConfigEager(); + + // Private constructor: prevents any other class from calling new AppConfigEager() + private AppConfigEager() { + System.out.println("Reading configuration from file..."); + // load properties, set up values, etc. + } + + // The only way to get the instance + public static AppConfigEager getInstance() { + return INSTANCE; + } + + public String getProperty(String key) { + return System.getProperty(key, "(not set)"); + } +} diff --git a/01-creational/singleton/AppConfigEnum.java b/01-creational/singleton/AppConfigEnum.java new file mode 100644 index 0000000..10d2f64 --- /dev/null +++ b/01-creational/singleton/AppConfigEnum.java @@ -0,0 +1,19 @@ +package singleton; + +public enum AppConfigEnum { + + INSTANCE; // The JVM creates this exactly once, period. + + // Enum instances can have fields and methods like any class + private final String configPath; + + AppConfigEnum() { + // Enum constructors run once, when the constant is first used + this.configPath = System.getProperty("config.path", "application.properties"); + System.out.println("Loading config from: " + configPath); + } + + public String getProperty(String key) { + return System.getProperty(key, "(not set)"); + } +} diff --git a/01-creational/singleton/AppConfigHolder.java b/01-creational/singleton/AppConfigHolder.java new file mode 100644 index 0000000..f79b1f3 --- /dev/null +++ b/01-creational/singleton/AppConfigHolder.java @@ -0,0 +1,19 @@ +package singleton; + +public class AppConfigHolder { + + private AppConfigHolder() { /* load config */ } + + // The JVM loads Holder only when getInstance() is first called. + // Class loading is inherently thread-safe — the JVM ensures it happens once. + // No synchronized, no volatile, no locks — just the JVM class loading guarantee. + private static class Holder { + static final AppConfigHolder INSTANCE = new AppConfigHolder(); + } + + public static AppConfigHolder getInstance() { + // This reference triggers Holder to load (if it hasn't already). + // After first call, Holder is already loaded and this is just a field read. + return Holder.INSTANCE; + } +} diff --git a/01-creational/singleton/AppConfigNaiveLazy.java b/01-creational/singleton/AppConfigNaiveLazy.java new file mode 100644 index 0000000..d8eca65 --- /dev/null +++ b/01-creational/singleton/AppConfigNaiveLazy.java @@ -0,0 +1,16 @@ +package singleton; + +public class AppConfigNaiveLazy { + + private static AppConfigNaiveLazy instance; // null until first request + + private AppConfigNaiveLazy() { /* load config */ } + + // BROKEN IN MULTITHREADED CODE — see explanation below + public static AppConfigNaiveLazy getInstance() { + if (instance == null) { // Thread A: checks, sees null + instance = new AppConfigNaiveLazy(); // Thread B: also sees null, also enters here + } // Both threads create a new AppConfigNaiveLazy! + return instance; + } +} diff --git a/01-creational/singleton/AppConfigSynchronized.java b/01-creational/singleton/AppConfigSynchronized.java new file mode 100644 index 0000000..1f0e76e --- /dev/null +++ b/01-creational/singleton/AppConfigSynchronized.java @@ -0,0 +1,16 @@ +package singleton; + +public class AppConfigSynchronized { + + private static AppConfigSynchronized instance; + + private AppConfigSynchronized() { /* load config */ } + + // synchronized: only one thread can execute this method at a time + public static synchronized AppConfigSynchronized getInstance() { + if (instance == null) { + instance = new AppConfigSynchronized(); // safe — no other thread can be here + } + return instance; + } +} diff --git a/01-creational/singleton/Main.java b/01-creational/singleton/Main.java index 58a676f..8827d8b 100644 --- a/01-creational/singleton/Main.java +++ b/01-creational/singleton/Main.java @@ -1,9 +1,36 @@ package singleton; + public class Main { public static void main(String[] args) { - AppConfig config1 = AppConfig.getInstance(); - AppConfig config2 = AppConfig.getInstance(); - System.out.println("Same instance? " + (config1 == config2)); // true - System.out.println("db.url = " + config1.getProperty("db.url")); + + System.out.println("--- Implementation 1: Eager ---"); + AppConfigEager eager1 = AppConfigEager.getInstance(); + AppConfigEager eager2 = AppConfigEager.getInstance(); + System.out.println("Same instance? " + (eager1 == eager2)); + System.out.println("db.url = " + eager1.getProperty("db.url")); + + System.out.println("\n--- Implementation 2: Naive Lazy ---"); + AppConfigNaiveLazy lazy1 = AppConfigNaiveLazy.getInstance(); + AppConfigNaiveLazy lazy2 = AppConfigNaiveLazy.getInstance(); + System.out.println("Same instance? " + (lazy1 == lazy2)); + + System.out.println("\n--- Implementation 3: Synchronized Method ---"); + AppConfigSynchronized sync1 = AppConfigSynchronized.getInstance(); + AppConfigSynchronized sync2 = AppConfigSynchronized.getInstance(); + System.out.println("Same instance? " + (sync1 == sync2)); + + System.out.println("\n--- Implementation 4: Double-Checked Locking ---"); + AppConfigDCL dcl1 = AppConfigDCL.getInstance(); + AppConfigDCL dcl2 = AppConfigDCL.getInstance(); + System.out.println("Same instance? " + (dcl1 == dcl2)); + + System.out.println("\n--- Implementation 5: Holder Idiom ---"); + AppConfigHolder holder1 = AppConfigHolder.getInstance(); + AppConfigHolder holder2 = AppConfigHolder.getInstance(); + System.out.println("Same instance? " + (holder1 == holder2)); + + System.out.println("\n--- Implementation 6: Enum ---"); + AppConfigEnum enumConfig = AppConfigEnum.INSTANCE; + System.out.println("db.url = " + enumConfig.getProperty("db.url")); } } diff --git a/README.md b/README.md index 04de40a..2f80d8c 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,26 @@ javac 01-creational/prototype/*.java -d out/prototype java -cp out/prototype prototype.Main ``` +### Singleton (`01-creational/singleton/`) + +| Post Section | File(s) | +|---|---| +| Implementation 1 — Eager Initialization | `AppConfigEager.java` | +| Implementation 2 — Naïve Lazy Initialization | `AppConfigNaiveLazy.java` | +| Implementation 3 — Synchronized Method | `AppConfigSynchronized.java` | +| Implementation 4 — Double-Checked Locking | `AppConfigDCL.java` | +| Implementation 5 — Initialization-on-Demand Holder | `AppConfigHolder.java` | +| Implementation 6 — Enum Singleton | `AppConfigEnum.java` | +| Running All Six Implementations Together | `Main.java` | + +Each implementation is renamed to its own class (e.g. `AppConfigEager`, `AppConfigHolder`) so all six can be compiled together in one package and compared directly. `Main.java` exercises all six in one run. The "Breaking via Reflection," "Breaking via Serialization," and Spring `@Component` snippets are illustrative only — they are not part of this repository's runnable example. + +Run it: +```bash +javac 01-creational/singleton/*.java -d out/singleton +java -cp out/singleton singleton.Main +``` + ## Reference - *Design Patterns: Elements of Reusable Object-Oriented Software* — Gamma, Helm, Johnson, Vlissides