preface
In some scenarios, we want to create objects that have only one instance in the entire software system, such as thread pools, log objects, caches, etc. Creating and saving a single object has two main functions: saving system resources; Prevents multiple objects from colliding. Singleton patterns ensure that only one instance object is created. Today, we’ll focus on eight implementations of the singleton pattern (in the Java language)
Private modifiers
As we all know, objects can be created by means of new. If the class does not restrict the creation of new objects, there is no guarantee that only one object will be created. Private modifies the constructor of a class, ensuring that the class cannot create arbitrary objects.
I. Hunghan (static constant)
The principle of implementing singleton pattern: using static constants to generate global unique instance characteristics when class loading
Specific code
Public class Singleton1 {private static Singleton1 instance = new Singleton1(); private static Singleton1 instance = new Singleton1(); public static Singleton1 getInstance() { return instance; } private Singleton1() {system.out.println (" Singleton1 "); } public static void main(String[] args) {system.out.println (" "); Singleton1 instance1 = Singleton1.getInstance(); Singleton1 instance2 = Singleton1.getInstance(); System.out.println(instance1 == instance2); } // Run the main method, and the result is as followsCopy the code
As you can see from the run result (one or two lines of output printed in order), the instance object we want to get is instantiated before we actually get it. (Static constants are assigned during class loading)
This is the downside of implementing singletons hungrier: you can’t load lazily.
2. Hunhun-style (static code block)
The implementation is basically the same as above, but the syntax is a little different. Instead of static variables, static code blocks are assigned directly.
Public class Singleton2 {static {instance = new Singleton2(); public class Singleton2 {static {instance = new Singleton2(); } private static Singleton2 instance; Private Singleton2() {system.out.println (" Singleton2 "); } public static Singleton2 getInstance() { return instance; } public static void main(String[] args) {system.out.println (" "); Singleton2 instance1 = Singleton2.getInstance(); Singleton2 instance2 = Singleton2.getInstance(); System.out.println(instance1 == instance2); } // Run the main method, and the result is as followsCopy the code
Three, lazy (conventional writing, thread is not safe)
In both cases, lazy loading is not supported. The next few methods are lazy loading methods. Let’s start with the simplest implementation
Public class Singleton3 {private static Singleton3 instance; public class Singleton3 {private static Singleton3 instance; Private Singleton3() {system.out.println (); private Singleton3() {system.out.println (); CurrentThread: "+ thread.currentthread ().getname ()); } public static Singleton3 getInstance() { if (instance == null) { instance = new Singleton3(); } return instance; } public static void main(String[] args) {system.out.println (" "); Singleton3 instance1 = Singleton3.getInstance(); Singleton3 instance2 = Singleton3.getInstance(); System.out.println(instance1 == instance2); } // The result of the run starts to demonstrate the normal lazy loading method to create singleton object: singleton mode implementation 3, lazy (conventional writing, thread unsafe). Current thread: main trueCopy the code
From the running results, this approach seems to be fine. That is to achieve lazy loading, and to ensure that the object is single.
In a different way, modify the main method:
Public static void main(String[] args) {system.out.println (" Start to show how to create a singleton with regular lazy loading: "); for (int i = 0; i < 50; i++) { new Thread(() -> { Singleton3.getInstance(); }).start(); }} // The result of the run (it is possible to run a few more times before the effect is similar) starts to show the normal lazy loading method to create the singleton object: singleton mode implementation 3, lazy (normal writing, thread unsafe). Current Thread: thread-1 singleton mode implementation 3, lazy (conventional writing, Thread unsafe). The current Thread is thread-0Copy the code
As can be seen from the results, the implementation of this singleton pattern is thread unsafe, in a multi-threaded environment, it is possible to create multiple instances.
Iv. Lazy (synchronous method, thread safe)
If (instance == null) is true before instance is instantiated, all threads continue to create instance objects. The simple and crude solution is to lock the getInstance method (using the synchronized keyword). Specific code:
Public class Singleton4 {private static Singleton4 instance; public class Singleton4 {private static Singleton4 instance; Private Singleton4() {system.out.println (); private Singleton4() {system.out.println (); CurrentThread: "+ thread.currentthread ().getname ()); } public static synchronized Singleton4 getInstance() { if (instance == null) { instance = new Singleton4(); } return instance; } public static void main(String[] args) {system.out.println (" "); for (int i = 0; i < 50; i++) { new Thread(() -> { Singleton4.getInstance(); }).start(); }}} // Run the result to start demo lazy load - synchronous method to create singleton object: singleton pattern implementation 4, lazy (synchronous method, thread safety). Current Thread: thread-1Copy the code
Although this method solves the thread safety problem, every time the instance object is acquired, it needs to be locked, which greatly affects the system running efficiency. The following implementation will gradually optimize the problem of low lazy loading efficiency under thread safety.
5. Lazyhead (synchronous code block)
In static methods, the lock size is too large, resulting in a waste of resources. Therefore, we try to reduce the lock granularity and add locks to code blocks.
Example code:
public class Singleton5 { private static Singleton5 instance; Private Singleton5() {system.out.println (); private Singleton5() {system.out.println (); CurrentThread: "+ thread.currentthread ().getname ()); } public static Singleton5 getInstance() { if (instance == null) { synchronized (Singleton5.class){ instance = new Singleton5(); } } return instance; } public static void main(String[] args) {system.out.println (" "); for (int i = 0; i < 50; i++) { new Thread(() -> { Singleton5.getInstance(); }).start(); }}} // The result of the run (it is possible to run several more times before the effect is similar) : start to show lazy load - synchronous code block to create singleton object: singleton pattern implementation 5, lazy (synchronous code block, thread safety). Current Thread: Thread-0 singleton pattern implementation 5, lazy (synchronous code block, Thread safe). Current Thread: thread-1Copy the code
As a result, this implementation is also thread unsafe. Reason analysis:
The key code
If (instance == null) {// synchronized (singleton5.class){// synchronized = new Singleton5(); // Line 3}}Copy the code
Although the lock is placed on line 2, this only ensures that line 3 can be executed by one thread at a time. Until the third line of code is executed, the different threads can still say if is true, and then go to the second line, wait for the thread with the lock to release it, and then continue to create the object after it has acquired it.
To be thread safe, modify as follows:
public static Singleton5 getInstance() {
synchronized (Singleton5.class) {
if (instance == null) {
instance = new Singleton5();
}
}
return instance;
}
Copy the code
However, this implementation is consistent with the fourth effect, where the granularity of the lock is the entire getInstance method.
Six, lazy (double check)
The first three lazy loading methods to achieve singleton, all have their own shortcomings, not thread safety or low efficiency to obtain singleton. The thread is unsafe because an instance is created by an existing thread and the instance is created again. The inefficiency is that the instance has already been created and the lock is applied to acquire the instance. Double checking avoids both problems.
The sample code
Public class Singleton6 {private static volatile Singleton6 instance; Private Singleton6() {system.out.println (); private Singleton6() {system.out.println (); CurrentThread: "+ thread.currentthread ().getname ()); } public static Singleton6 getInstance() {if (instance == null) {system.out.println (" try to create an instance..."); ); synchronized (Singleton6.class){ if (instance == null) { instance = new Singleton6(); } } } return instance; } public static void main(String[] args) {system.out.println (" "); for (int i = 0; i < 50; i++) { new Thread(() -> { Singleton6.getInstance(); }).start(); }}} // Run results to start demo lazy load - double check to create singleton object: try to create instance... Singleton pattern implementation 6, lazy (double checked). The current Thread is thread-0Copy the code
In particular, we use the keyword “volatile” for static variables. For details, see the article “Double-checked Locking and Delayed Initialization”.
Static inner classes
We can also implement thread-safe singletons using static inner classes
The sample code
Public class Singleton7 {private Singleton7(){system.out.println (" public class Singleton7 {private Singleton7(){system.out.println (); CurrentThread: "+ thread.currentthread ().getname ()); } private static class InstanceHolder{ private static Singleton7 instance = new Singleton7(); } public static Singleton7 getInstance() { return InstanceHolder.instance; } public static void main(String[] args) {system.out.println (" "); Singleton7 instance1 = Singleton7.getInstance(); Singleton7 instance2 = Singleton7.getInstance(); System.out.println(instance1 == instance2); } // Run results to start demo static inner class create singleton object: singleton pattern implementation 7 static inner class. Current thread: main trueCopy the code
The JVM helps us ensure thread-safe inner class creation
Eight, enumeration method
Enumerations are natural singleton cases in the JVM, so implementing singleton cases using enumerations is also thread-safe. The book Effective Java advocates creating singletons using enumerations
The sample code
public enum Singleton8 { INSTANCE(); Singleton8(){system.out.println (" Singleton8, enumeration "); }}Copy the code
conclusion
There are a variety of implementations of singleton mode to ensure thread safety and operation efficiency (the third method in the article is not safe, and the fourth and fifth methods are inefficient)), the actual effect of various implementations is not very different, you can choose your own convenient implementation! The “lazy loading” and “double checking” ideas are often used in our development. I hope you understand these two ideas well.