1. What is singleton mode
Singleton pattern refers to the existence of only one instance throughout the entire life of the application. Singleton pattern is a widely used design pattern. It has many benefits, which can avoid duplicate creation of instance objects, reduce the system overhead of creating instances, and save memory.
There are three requirements for singleton mode:
2. The difference between singleton pattern and static class
First, let’s understand what a static class is. A static class means that a class has static methods and static fields. The constructor is modified by private, so it cannot be instantiated. The Math class is a static class.
After knowing what a static class is, let’s talk about the difference between them:
1) First of all, the singleton pattern will provide you with a globally unique object. The static class only provides you with many static methods. These methods do not need to be created, and can be called directly through the class;
2) The singleton pattern has higher flexibility, and methods can be override, because static classes are all static methods, so they cannot be override;
3) If it is a very heavy object, the singleton pattern can be lazy to load, but static classes cannot do it;
Then, static classes should be used, and when should we use singleton mode? First of all, if you just want to use some tool methods, it is best to use static classes. Static analogies are faster than singleton classes, because static binding is performed during the compilation period. If you want to maintain status information or access resources, you should use singleton mode. It can also be said that when you need object-oriented capabilities (such as inheritance, polymorphism), choose singleton classes, and when you only provide some methods, choose static classes.
3. How to implement singleton mode
1. Hungry Man Mode
The so-called hungry mode is to load immediately. Generally, instances have been generated before calling the getInstancef method, which means that it has been generated when the class is loaded. The disadvantage of this model is very obvious, which is that it occupies resources. When the singleton class is large, we actually want to use it and then generate instances. Therefore, this method is suitable for classes that occupy less resources and will be used during initialization.
class SingletonHungary { private static SingletonHungary singletonHungary = new SingletonHungary(); //Set the constructor to private to prohibit instantiation through new private SingletonHungary() { } public static SingletonHungary getInstance() { return singletonHungary; }}2. Lazy mode
Lazy mode is lazy loading, also called lazy loading. Create an instance when the program needs to be used, so that the memory will not be wasted. For the lazy mode, here are 5 implementation methods. Some implementation methods are thread-insecure, which means that resource synchronization problems may occur in a multi-threaded concurrency environment.
First of all, the first method is that there is no problem in single thread, but there will be problems in multi-threading.
// Lazy implementation of singleton mode 1-thread unsafe class SingletonLazy1 { private static SingletonLazy1 singletonLazy; private SingletonLazy1() { } public static SingletonLazy1 getInstance() { if (null == singletonLazy) { try { // Simulate some preparation before creating the object Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } singletonLazy = new SingletonLazy1(); } return singletonLazy; }}Let's simulate 10 asynchronous threads to test:
public class SingletonLazyTest { public static void main(String[] args) { Thread2[] ThreadArr = new Thread2[10]; for (int i = 0; i < ThreadArr.length; i++) { ThreadArr[i] = new Thread2(); ThreadArr[i].start(); } }}// Test thread class Thread2 extends Thread { @Override public void run() { System.out.println(SingletonLazy1.getInstance().hashCode()); }}Running results:
124191239
124191239
872096466
1603289047
1698032342
1913667618
371739364
124191239
1723650563
367137303
You can see that their hashCodes are not all the same, which means that multiple objects are generated in a multi-threaded environment, which does not meet the requirements of the singleton pattern.
So how to make thread safe? In the second method, we use the synchronized keyword to synchronize the getInstance method.
// Singleton mode lazy implementation 2--thread safety// By setting the synchronization method, the efficiency is too low, the whole method is locked class SingletonLazy2 { private static SingletonLazy2 singletonLazy; private SingletonLazy2() { } public static synchronized SingletonLazy2 getInstance() { try { if (null == singletonLazy) { // Simulate some preparation before creating the object Thread.sleep(1000); singletonLazy = new SingletonLazy2(); } } catch (InterruptedException e) { e.printStackTrace(); } return singletonLazy; }}Using the above test class, the test results:
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
As can be seen, this method achieves thread safety. However, the disadvantage is that the efficiency is too low and it runs synchronously. If the next thread wants to obtain the object, it must wait for the previous thread to release before it can continue to execute.
Then we can not lock the method, but lock the code inside, which can also achieve thread safety. But this method is the same as the synchronization method, and it also runs synchronously and has very low efficiency.
// SingletonLazy Implementation 3--Thread Safety// By setting synchronous code blocks, the efficiency is too low, and the entire code block is locked class SingletonLazy3 { private static SingletonLazy3 singletonLazy; private SingletonLazy3() { } public static SingletonLazy3 getInstance() { try { synchronized (SingletonLazy3.class) { if (null == singletonLazy) { // Simulate to do some preparation before creating an object Thread.sleep(1000); singletonLazy = new SingletonLazy3(); } } } catch (InterruptedException e) { // TODO: handle exception } return singletonLazy; }}Let's continue to optimize the code. We only lock the code that creates the object, but can this ensure thread safety?
// Lazy implementation of singleton mode 4--thread unsafe// Only the code that creates instances is synchronized by setting synchronization code blocks// But there are still thread safety issues class SingletonLazy4 { private static SingletonLazy4 singletonLazy; private SingletonLazy4() { } public static SingletonLazy4 getInstance() { try { if (null == singletonLazy) { //Code 1 //Simulate to do some preparation before creating an object Thread.sleep(1000); synchronized (SingletonLazy4.class) { singletonLazy = new SingletonLazy4(); //Code 2 } } } catch (InterruptedException e) { // TODO: handle exception } return singletonLazy; }}Let's take a look at the running results:
1210004989
1425839054
1723650563
389001266
1356914048
389001266
1560241484
278778395
124191239
367137303
Judging from the results, this method cannot guarantee thread safety. Why? Let's assume that two threads A and B go to 'code 1' at the same time, because the object is still empty at this time, so both can enter the method. Thread A first grabs the lock and creates the object. After thread B gets the lock, it will also go to 'code 2' when it is released, and an object is created. Therefore, a singleton cannot be guaranteed in a multi-threaded environment.
Let's continue to optimize. Since there is a problem with the above method, we can just make a null judgment in the synchronization code block. This method is our DCL double check lock mechanism.
//Slazy man in singleton mode implements 5-thread safety // By setting the synchronization code block, use the DCL double check lock mechanism //Using the double check lock mechanism successfully solves the thread insecurity and efficiency problems implemented by the lazy man in singleton mode //DCL is also a solution used by most multithreading combined with singleton mode //The function of the first if judgment: to improve the efficiency of the program. When the SingletonLazy5 object is created, when the SingletonLazy5 object is obtained, there is no need to verify the lock of the synchronization code block and the subsequent code, and directly return the SingletonLazy5 object //The function of the second if judgment: to solve the security problems under multithreading, that is, to ensure the uniqueness of the object. class SingletonLazy5 { private static volatile SingletonLazy5 singletonLazy; private SingletonLazy5() { } public static SingletonLazy5 getInstance() { try { if (null == singletonLazy) { // Simulate some preparation before creating the object Thread.sleep(1000); synchronized (SingletonLazy5.class) { if(null == singletonLazy) { singletonLazy = new SingletonLazy5(); } } } } catch (InterruptedException e) { } return singletonLazy; }}Running results:
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
We can see that the DCL double check lock mechanism solves the efficiency and thread safety issues of lazy loading singleton mode. This is also the method we use most often.
volatile keyword
Here I noticed that when defining singletonLazy, the volatile keyword is used. This is to prevent instructions from reordering. Why do we need to do this? Let’s take a look at a scenario:
The code goes to singletonLazy = new SingletonLazy5(); It seems to be a sentence, but this is not an atomic operation (either all are executed, or all are not executed, and half cannot be executed). This sentence is compiled into 8 assembly instructions, and roughly 3 things are done:
1. Allocate memory to the SingletonLazy5 instance.
2. Initialize the SingletonLazy5 constructor
3. Point the singletonLazy object to the allocated memory space (note that this instance is not null).
Since the Java compiler allows the processor to execute out-of-order (out-order), and the order of cache, registers to main memory writeback in JMM (Java Memory Medel) before JDK1.5, the order of the second and third points above cannot be guaranteed. That is to say, the execution order may be 1-2-3 or 1-3-2. If it is the latter, and before 3 is executed and 2 is not executed, it is switched to thread 2. At this time, singletonLazy has already executed the third point in thread one, singletonLazy is already non-empty, so thread two directly takes singletonLazy, then uses it, and then naturally reports an error. Moreover, this kind of error that is difficult to track and difficult to reproduce may not be found in the last week of debugging.
The writing method of DCL to implement singletons is recommended in many technical books and textbooks (including books based on previous versions of JDK1.4), but it is actually not completely correct. Indeed, DCL is feasible in some languages (such as C), depending on whether the order of 2 and 3 steps can be guaranteed. After JDK1.5, the official has noticed this problem, so the JMM has been adjusted and the volatile keyword has been concreted. Therefore, if JDK is a version of 1.5 or later, you only need to add the volatile keyword to the definition of singletonLazy, which can ensure that singletonLazy is read from the main memory every time, and reordering can be prohibited, and you can use the DCL writing method to complete the singleton mode. Of course, volatile will more or less affect performance. The most important thing is that we also need to consider JDK1.42 and previous versions, so the improvement of singleton pattern writing is still continuing.
3. Static inner class
Based on the above considerations, we can use static inner classes to implement singleton pattern, the code is as follows:
//Implement singleton mode with static inner classes-thread safety class SingletonStaticInner { private SingletonStaticInner() { } private static class SingletonInner { private static SingletonStaticInner singletonStaticInner = new SingletonStaticInner(); } public static SingletonStaticInner getInstance() { try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } return SingletonInner.singletonStaticInner; }}It can be seen that we do not explicitly perform any synchronization operations in this way, so how does it ensure thread safety? Like the Hungry Man mode, it is a feature that JVM ensures that the static members of the class can only be loaded once, so that there is only one instance object from the JVM level. So the question is, what is the difference between this method and the hungry man model? Isn't it loading immediately? In fact, when a class is loaded, its inner class will not be loaded at the same time. A class is loaded, which occurs when and only if one of its static members (static domains, constructors, static methods, etc.) is called.
It can be said that this method is the optimal solution to implement the singleton pattern.
4. Static code blocks
Here is a static code block implementation singleton pattern. This method is similar to the first one, and it is also a hungry man model.
//Use static code blocks to implement singleton mode class SingletonStaticBlock { private static SingletonStaticBlock singletonStaticBlock; static { singletonStaticBlock = new SingletonStaticBlock(); } public static SingletonStaticBlock getInstance() { return singletonStaticBlock; }}5. Serialization and deserialization
Why does LZ recommend serialization and deserialization? Because although singleton mode can ensure thread safety, multiple objects will be generated in the case of serialization and deserialization. Run the following test class,
public class SingletonStaticInnerSerializeTest { public static void main(String[] args) { try { SingletonStaticInnerSerialize serialize = SingletonStaticInnerSerialize.getInstance(); System.out.println(serialize.hashCode()); //Serialize FileOutputStream fo = new FileOutputStream("tem"); ObjectOutputStream oo = new ObjectOutputStream(fo); oo.writeObject(serialize); oo.close(); fo.close(); //Deserialize FileInputStream fi = new FileInputStream("tem"); ObjectInputStream oi = new ObjectInputStream(fi); SingletonStaticInnerSerialize serialize2 = (SingletonStaticInnerSerialize) oi.readObject(); oi.close(); fi.close(); System.out.println(serialize2.hashCode()); } catch (Exception e) { e.printStackTrace(); } }}//Use anonymous internal classes to implement singleton pattern. When encountering serialization and deserialization, the same instance is not obtained.//Solve this problem is to use the readResolve method during serialization, that is, remove part of the comments. class SingletonStaticInnerSerialize implements Serializable { /** * March 28, 2018*/ private static final long serialVersionUID = 1L; private static class InnerClass { private static SingletonStaticInnerSerialize singletonStaticInnerSerialize = new SingletonStaticInnerSerialize(); } public static SingletonStaticInnerSerialize getInstance() { return InnerClass.singletonStaticInnerSerialize; }// protected Object readResolve() {// System.out.println("The readResolve method was called");// return InnerClass.singletonStaticInnerSerialize;// }}You can see:
865113938
1078694789
The results show that it is indeed two different object instances that violate the singleton pattern. So how to solve this problem? The solution is to use the readResolve() method in deserialization, remove the above comment code, and run it again:
865113938
The readResolve method was called
865113938
The question is, who is the sacred way of the readResolve() method? In fact, when the JVM deserializes and "assembles" a new object from memory, it will automatically call this readResolve method to return the object we specified, and the singleton rules are guaranteed. The emergence of readResolve() allows programmers to control the objects obtained through deserialization on their own.
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.