/*
* Singleton — one shared instance of a class.
*
* Use it for a logger, config, or one in-memory store.
* Avoid it when tests need to mock the object.
*
* These are different ways to build a singleton. Not all of them are safe.
*
* 1. Lazy
* Create on first getInstance().
* Broken under threads: two callers can both see null and both call new.
*
* 2. synchronized getInstance()
* Same lazy idea, lock on the whole method.
* Correct, but every later call still pays for the lock.
*
* 3. Eager
* Instance created at field declaration. getInstance only returns it.
* Private constructor is still required, otherwise anyone can call new.
* Cannot pass runtime constructor args. If the constructor throws,
* the app never starts.
*
* 4. Double-check locking
* Check null, lock, check null again.
* Correct and faster after the object exists.
* volatile is visibility across threads, not locking.
*
* 5. Enum ← use this in Java
* Best approach here. The language gives you one instance.
* Serialization cannot manufacture a second object, which is how
* a normal class singleton can break.
*
* BookingStore below uses double-check locking as a worked example of
* a shared in-memory map (one store, two services, same bookings).
* If this were production Java, BookingStore would be an enum instead.
*/
enum EagerDatabase {
INSTANCE;
public void query
(String sql
) { System.
out.
println("[eager-enum] " + sql
); }
}
class LazyUnsafeDatabase {
private static LazyUnsafeDatabase instance;
private LazyUnsafeDatabase() {}
public static LazyUnsafeDatabase getInstance() {
if (instance == null) {
instance = new LazyUnsafeDatabase();
}
return instance;
}
}
class SynchronizedDatabase {
private static SynchronizedDatabase instance;
private SynchronizedDatabase() {}
public static synchronized SynchronizedDatabase getInstance() {
if (instance == null) {
instance = new SynchronizedDatabase();
}
return instance;
}
}
class EagerDatabaseClass {
private static final EagerDatabaseClass INSTANCE = new EagerDatabaseClass();
private EagerDatabaseClass() {}
public static EagerDatabaseClass getInstance() {
return INSTANCE;
}
}
class DoubleCheckDatabase {
private static DoubleCheckDatabase instance;
private DoubleCheckDatabase() {}
public static DoubleCheckDatabase getInstance() {
if (instance == null) {
synchronized (DoubleCheckDatabase.class) {
if (instance == null) {
instance = new DoubleCheckDatabase();
}
}
}
return instance;
}
}
/*
* A place Singleton actually fits: one in-memory booking map that every
* service must see. Two instances would mean two different "databases".
*/
class BookingStore {
private static BookingStore instance;
private final java.
util.
Map<String, String
> bookings
;
private BookingStore() {
bookings
= new java.
util.
HashMap<String, String
>(); }
public static BookingStore getInstance() {
if (instance == null) {
synchronized (BookingStore.class) {
if (instance == null) {
instance = new BookingStore();
}
}
}
return instance;
}
bookings.put(seat, user);
}
return bookings.get(seat);
}
public int size() {
return bookings.size();
}
}
public class Main {
public static void main
(String[] args
) { EagerDatabaseClass a = EagerDatabaseClass.getInstance();
EagerDatabaseClass b = EagerDatabaseClass.getInstance();
System.
out.
println("eager same object? " + (a
== b
));
DoubleCheckDatabase c = DoubleCheckDatabase.getInstance();
DoubleCheckDatabase d = DoubleCheckDatabase.getInstance();
System.
out.
println("double-check same object? " + (c
== d
));
EagerDatabase.INSTANCE.query("select 1");
System.
out.
println("enum same object? " + (EagerDatabase.
INSTANCE == EagerDatabase.
INSTANCE));
BookingStore payments = BookingStore.getInstance();
BookingStore catalogue = BookingStore.getInstance();
payments.book("A12", "Harsha");
System.
out.
println("catalogue sees the same booking? " + catalogue.
holderOf("A12")); System.
out.
println("store size from either reference: " + catalogue.
size()); System.
out.
println("payments == catalogue? " + (payments
== catalogue
)); }
}