Rookies learn the singleton pattern of Java design pattern
We are not unfamiliar with the singleton mode, and we all know why it can be divided into lazy type and hungry type. But do you have a thorough understanding of the singleton pattern? Today, I'd like to take a look at the single case in my eyes, which may be different from your understanding.
Here is a simple example:
It is easy to see that the above code is unsafe in the case of multithreading. When two threads enter if (instance = = null), both threads judge that instance is empty, and then two instances will be obtained. This is not the single example we want.
Next, we use locking to realize mutual exclusion, so as to ensure the implementation of singletons.
Adding synchronized does ensure thread safety, but is this the best way? Obviously, it is not, because in this way, every call to getInstance () method will be locked, and we only need to lock when we call getInstance () for the first time. This obviously affects the performance of our program. We continue to look for better ways.
After analysis, it is found that thread safety can be guaranteed only by ensuring that instance = new singleton() is thread mutually exclusive. Therefore, the following version is available:
This time, it seems that the thread safety problem is solved without locking every time getInstance () is called, resulting in performance degradation. It looks like a perfect solution, but is it?
Unfortunately, the fact is not as perfect as we think. There is a mechanism called "out of order writes" in the memory model of the Java platform. It is this mechanism that leads to the failure of the double check locking method. The key to this problem is line 5 of the above code: instance = new singleton(); This line actually does two things: 1. Call the constructor to create an instance. 2. Assign this instance to the instance variable. The problem is that the two-step JVM does not guarantee the order. in other words. Instance may have been set to non empty before calling the constructor. Let's analyze it together:
Suppose there are two threads a and B
1. Thread a enters the getInstance () method. 2. Because instance is empty at this time, thread a enters the synchronized block. 3. Thread a executes instance = new singleton(); Set the instance variable instance to non null. (note that it is before calling the constructor.) 4. Thread a exits and thread B enters. 5. Thread B checks whether instance is empty. At this time, it is not empty (it is set to non empty by thread a in the third step). Thread B returns the reference of instance. (the problem arises. At this time, the instance reference is not an instance of singleton because the constructor is not called.) 6. Thread B exits and thread a enters. 7. Thread a continues to call the constructor, completes the initialization of instance, and then returns.
Isn't there a good way? There must be some good methods. Let's continue to explore!
1. Thread a enters the getInstance () method. 2. Because instance is empty, thread a enters the first synchronized block at position / / 1. 3. Thread a executes the code at position / / 2 and assigns instance to the local variable temp. Instance is empty, so temp is also empty. 4. Because temp is empty, thread a enters the second synchronized block at position / / 3. (later, I think this lock is a little redundant) 5. Thread a executes the code at / / 4 and sets temp to non empty, but has not called the construction method yet! (the "write out of order" problem) 6. If thread a is blocked, thread B enters the getInstance () method. 7. Because instance is empty, thread B attempts to enter the first synchronized block. But because thread a is already inside. So I can't get in. Thread B is blocked. 8. Thread a activates and continues to execute the code at position / / 4. Call the constructor. Generate an instance. 9. Assign the instance reference of temp to instance. Exit both synchronized blocks. Returns an instance. 10. Thread B activates and enters the first synchronized block. 11. Thread B executes the code at position / / 2 and assigns the instance to the temp local variable. 12. Thread B judges that the local variable temp is not empty, so it skips the if block. Returns the instance.
So far, we have solved the above problems, but we suddenly found that in order to solve the thread safety problem, it gives people the feeling that they are wrapped with a lot of wool It's messy, so let's simplify it:
Seeing the above code, I feel that the world is quiet. However, this method adopts the hungry man method, which is to declare the singleton object in advance. One disadvantage of this method is that if the constructed singleton is large and is not used after construction, it will lead to a waste of resources.
Is there a perfect way? Continue:
Lazy (avoiding the above waste of resources), thread safe and simple code. Because the Java mechanism stipulates that the inner class singletonholder will be loaded only when the getInstance () method is called for the first time (lazy is implemented), and its loading process is thread safe (thread safe). Instance is instantiated when the inner class is loaded.
Let's briefly talk about the unordered write mentioned above, which is a feature of the JVM, such as declaring two variables, string a; String b; The JVM may load a or B first. Similarly, instance = new singleton(); Instance may be set to non null before calling Singleton's constructor. Many people will question that if an object of singleton has not been instantiated, how can instance become non empty? What is its value now? To understand this problem, you should understand that instance = new singleton(); How to execute this sentence is explained below with a pseudo code:
This shows that when a thread executes to instance = MEM; Instance is already non empty at this time. If another thread enters the program and judges that instance is non empty, it will directly jump to return instance; At this time, the singleton constructor has not called instance, and the current value is allocate(); The memory object returned. So the second thread gets not an object of singleton, but a memory object.
The above is my little thinking and understanding of the single case mode. I warmly welcome all great gods to guide and criticize.