Singleton is a creational design pattern that lets you ensure that a class has only one instance, while providing a global access point to this instance.
Problem
The Singleton pattern solves two problems at the same time, violating the Single Responsibility Principle:
Ensure that a class has just a single instance.
Provide a global access point to that instance.
Solution
All implementations of the Singleton have these two steps in common:
Make the default constructor private, to prevent other objects from using the new operator with the Singleton class.
Create a static creation method that acts as a constructor. Under the hood, this method calls the private constructor to create an object and saves it in a static field. All following calls to this method return the cached object.
If your code has access to the Singleton class, then it’s able to call the Singleton’s static method. So whenever that method is called, the same object is always returned.
Singleton in Java
Naïve Singleton (single-threaded)
It’s pretty easy to implement a sloppy Singleton. You just need to hide the constructor and implement a static creation method.
publicclassDemoSingleThread { publicstaticvoidmain(String[] args) { System.out.println("If you see the same value, then singleton was reused (yay!)" + "\n" + "If you see different values, then 2 singletons were created (booo!!)" + "\n\n" + "RESULT:" + "\n"); Singletonsingleton= Singleton.getInstance("FOO"); SingletonanotherSingleton= Singleton.getInstance("BAR"); System.out.println(singleton.value); System.out.println(anotherSingleton.value); } }
OutputDemoSingleThread.txt: Execution result
1 2 3 4 5 6 7
If you see the same value, then singleton was reused (yay!) If you see different values, then 2 singletons were created (booo!!)
RESULT:
FOO FOO
Naïve Singleton (multithreaded)
The same class behaves incorrectly in a multithreaded environment. Multiple threads can call the creation method simultaneously and get several instances of Singleton class.
publicclassDemoMultiThread { publicstaticvoidmain(String[] args) { System.out.println("If you see the same value, then singleton was reused (yay!)" + "\n" + "If you see different values, then 2 singletons were created (booo!!)" + "\n\n" + "RESULT:" + "\n"); ThreadthreadFoo=newThread(newThreadFoo()); ThreadthreadBar=newThread(newThreadBar()); threadFoo.start(); threadBar.start(); }
publicfinalclassSingleton { // The field must be declared volatile so that double check lock would work // correctly. privatestaticvolatile Singleton instance;
publicstatic Singleton getInstance(String value) { // The approach taken here is called double-checked locking (DCL). It // exists to prevent race condition between multiple threads that may // attempt to get singleton instance at the same time, creating separate // instances as a result. // // It may seem that having the `result` variable here is completely // pointless. There is, however, a very important caveat when // implementing double-checked locking in Java, which is solved by // introducing this local variable. // // You can read more info DCL issues in Java here: // https://refactoring.guru/java-dcl-issue Singletonresult= instance; if (result != null) { return result; } synchronized(Singleton.class) { if (instance == null) { instance = newSingleton(value); } return instance; } } }
publicclassDemoMultiThread { publicstaticvoidmain(String[] args) { System.out.println("If you see the same value, then singleton was reused (yay!)" + "\n" + "If you see different values, then 2 singletons were created (booo!!)" + "\n\n" + "RESULT:" + "\n"); ThreadthreadFoo=newThread(newThreadFoo()); ThreadthreadBar=newThread(newThreadBar()); threadFoo.start(); threadBar.start(); }