fork download
  1. /*
  2.  * Singleton — one shared instance of a class.
  3.  *
  4.  * Use it for a logger, config, or one in-memory store.
  5.  * Avoid it when tests need to mock the object.
  6.  *
  7.  * These are different ways to build a singleton. Not all of them are safe.
  8.  *
  9.  * 1. Lazy
  10.  * Create on first getInstance().
  11.  * Broken under threads: two callers can both see null and both call new.
  12.  *
  13.  * 2. synchronized getInstance()
  14.  * Same lazy idea, lock on the whole method.
  15.  * Correct, but every later call still pays for the lock.
  16.  *
  17.  * 3. Eager
  18.  * Instance created at field declaration. getInstance only returns it.
  19.  * Private constructor is still required, otherwise anyone can call new.
  20.  * Cannot pass runtime constructor args. If the constructor throws,
  21.  * the app never starts.
  22.  *
  23.  * 4. Double-check locking
  24.  * Check null, lock, check null again.
  25.  * Correct and faster after the object exists.
  26.  * volatile is visibility across threads, not locking.
  27.  *
  28.  * 5. Enum ← use this in Java
  29.  * Best approach here. The language gives you one instance.
  30.  * Serialization cannot manufacture a second object, which is how
  31.  * a normal class singleton can break.
  32.  *
  33.  * BookingStore below uses double-check locking as a worked example of
  34.  * a shared in-memory map (one store, two services, same bookings).
  35.  * If this were production Java, BookingStore would be an enum instead.
  36.  */
  37.  
  38. enum EagerDatabase {
  39. INSTANCE;
  40.  
  41. public void query(String sql) {
  42. System.out.println("[eager-enum] " + sql);
  43. }
  44. }
  45.  
  46. class LazyUnsafeDatabase {
  47. private static LazyUnsafeDatabase instance;
  48.  
  49. private LazyUnsafeDatabase() {}
  50.  
  51. public static LazyUnsafeDatabase getInstance() {
  52. if (instance == null) {
  53. instance = new LazyUnsafeDatabase();
  54. }
  55. return instance;
  56. }
  57. }
  58.  
  59. class SynchronizedDatabase {
  60. private static SynchronizedDatabase instance;
  61.  
  62. private SynchronizedDatabase() {}
  63.  
  64. public static synchronized SynchronizedDatabase getInstance() {
  65. if (instance == null) {
  66. instance = new SynchronizedDatabase();
  67. }
  68. return instance;
  69. }
  70. }
  71.  
  72. class EagerDatabaseClass {
  73. private static final EagerDatabaseClass INSTANCE = new EagerDatabaseClass();
  74.  
  75. private EagerDatabaseClass() {}
  76.  
  77. public static EagerDatabaseClass getInstance() {
  78. return INSTANCE;
  79. }
  80. }
  81.  
  82. class DoubleCheckDatabase {
  83. private static DoubleCheckDatabase instance;
  84.  
  85. private DoubleCheckDatabase() {}
  86.  
  87. public static DoubleCheckDatabase getInstance() {
  88. if (instance == null) {
  89. synchronized (DoubleCheckDatabase.class) {
  90. if (instance == null) {
  91. instance = new DoubleCheckDatabase();
  92. }
  93. }
  94. }
  95. return instance;
  96. }
  97. }
  98.  
  99. /*
  100.  * A place Singleton actually fits: one in-memory booking map that every
  101.  * service must see. Two instances would mean two different "databases".
  102.  */
  103. class BookingStore {
  104. private static BookingStore instance;
  105. private final java.util.Map<String, String> bookings;
  106.  
  107. private BookingStore() {
  108. bookings = new java.util.HashMap<String, String>();
  109. }
  110.  
  111. public static BookingStore getInstance() {
  112. if (instance == null) {
  113. synchronized (BookingStore.class) {
  114. if (instance == null) {
  115. instance = new BookingStore();
  116. }
  117. }
  118. }
  119. return instance;
  120. }
  121.  
  122. public void book(String seat, String user) {
  123. bookings.put(seat, user);
  124. }
  125.  
  126. public String holderOf(String seat) {
  127. return bookings.get(seat);
  128. }
  129.  
  130. public int size() {
  131. return bookings.size();
  132. }
  133. }
  134.  
  135. public class Main {
  136. public static void main(String[] args) {
  137. EagerDatabaseClass a = EagerDatabaseClass.getInstance();
  138. EagerDatabaseClass b = EagerDatabaseClass.getInstance();
  139. System.out.println("eager same object? " + (a == b));
  140.  
  141. DoubleCheckDatabase c = DoubleCheckDatabase.getInstance();
  142. DoubleCheckDatabase d = DoubleCheckDatabase.getInstance();
  143. System.out.println("double-check same object? " + (c == d));
  144.  
  145. EagerDatabase.INSTANCE.query("select 1");
  146. System.out.println("enum same object? " + (EagerDatabase.INSTANCE == EagerDatabase.INSTANCE));
  147.  
  148. BookingStore payments = BookingStore.getInstance();
  149. BookingStore catalogue = BookingStore.getInstance();
  150. payments.book("A12", "Harsha");
  151. System.out.println("catalogue sees the same booking? " + catalogue.holderOf("A12"));
  152. System.out.println("store size from either reference: " + catalogue.size());
  153. System.out.println("payments == catalogue? " + (payments == catalogue));
  154. }
  155. }
Success #stdin #stdout 0.14s 55412KB
stdin
Standard input is empty
stdout
eager same object? true
double-check same object? true
[eager-enum] select 1
enum same object? true
catalogue sees the same booking? Harsha
store size from either reference: 1
payments == catalogue? true