In development, if an instance is created that consumes a lot of system resources, we typically use lazy loading mechanisms, which means that this instance is created only when this instance is used, which is widely used in singleton mode. The implementation of this mechanism in single-threaded environment is very simple, but there are hidden dangers in the multi-threaded environment. This article focuses on the lazy loading mechanism and its use in multi-threaded environments. (author Numberzero, refer to the IBM article "double-checked locking and the Singleton pattern", welcome reprint and discussion)
1. Lazy loading of a singleton mode
usually when we design a singleton class, we construct the class inside the class (either by constructor or directly at the definition), and provide a static getinstance method to provide a way to get the singleton object. For example :
public class Singleton { private static Singleton instance = new Singleton (); Private Singleton () { ... } public static Singleton getinstance () { return instance; }
The disadvantage of this code is that the first time the class is loaded with the creation of the singleton instance, the result is different from what we expected because it might not be the time we need this instance to create the instance. At the same time, if the creation of this singleton instance consumes system resources, and the application never uses the singleton instance, then the system resources created singleton consume are wasted.
To avoid this, we usually use lazy loading mechanisms, which are used to create them. The lazy loading code for the above code is as follows:
public class singleton{ private static Singleton instance = null; Private Singleton () { ... } public static Singleton getinstance () { if (instance = = null) instance = new Singleton (); return instance; } }
2. Problem of lazy loading in multi-threading
First extract the Lazy loaded code:
public static Singleton getinstance () { if (instance = = null) instance = new Singleton (); return instance; }
This is if two threads A and B execute the method at the same time, and then execute as follows:
1. A enters if judgment, at this time foo is null, so enter if
2. b enters if judgment, at which point A has not yet created Foo, so foo is also null, so B also enters if
3. A creates a Foo and returns
4. b also creates a Foo and returns
The problem arises and our singleton was created two times, which is not what we expected.
3 solutions and their existing problems
3.1 Using the class lock mechanism
The most straightforward solution to the above problem is to add a synchronize prefix to the GetInstance method so that only one off-the-shelf call getinstance method is allowed at a time:
public static synchronized Singleton getinstance () { if (instance = = null) instance = new Singleton (); return instance; }
This solution does prevent errors, but it does affect performance: Singleton locks must be obtained each time the GetInstance method is called, and in fact, after the singleton instance is created, subsequent requests do not need to use the mutex again.
3.2 double-checked Locking
In order to solve the above problems, some people have put forward the double-checked locking solution.
public static Singleton getinstance () { if (instance = = null) synchronized (instance) { if (instance = = null) C4/>instance = new Singleton (); } return instance;
Let's take a look at how this code works: first, when a thread makes a request, it checks whether instance is null, and if not, returns its contents directly, thus avoiding the resources required to enter the synchronized block. Second, even if the situation mentioned in section 2nd occurs, and two threads enter the first if judgment at the same time, they must also execute the code in the synchronized block sequentially, and the first thread that enters the code block creates a new singleton instance. Subsequent threads do not create redundant instances because they cannot be judged by IF.
The above description seems to have solved all the problems we face, but in fact, from the JVM's point of view, the code can still be wrong.
For the JVM, it executes a Java instruction. creating objects and assigning operations in Java directives is done separately, that is, instance = new Singleton (), and the statements are executed in two steps. However, the JVM does not guarantee the sequencing of the two operations, which means that it is possible for the JVM to allocate space for the new singleton instance and then assign the value directly to the instance member before initializing the singleton instance. This makes the error possible, and we still take the A, b two threads as an example:
1. A and B threads enter the first if judgment at the same time
2. A first enters the synchronized block, because instance is null, so it executes instance = new Singleton ();
3. Due to the optimization mechanism within the JVM, the JVM first draws some blank memory allocated to the singleton instance and assigns it to the instance member (note that at this point the JVM does not start initializing the instance), and a leaves the synchronized block.
4. b enters the synchronized block because instance is not NULL at this time, so it immediately leaves the synchronized block and returns the result to the program that called the method.
5. At this point the B thread intends to use the singleton instance, but finds that it has not been initialized and that the error occurred.
4 Implementing a singleton pattern in a multithreaded environment through an internal class
The best and most convenient workaround is to implement slow loading and do not want to be mutually exclusive when calling GetInstance:
public class singleton{ private Singleton () { ... } private static class singletoncontainer{ private static Singleton instance = new Singleton (); } public static Singleton getinstance () { return singletoncontainer.instance; } }
The mechanism inside the JVM guarantees that when a class is loaded, the loading process of the class is thread-exclusive. So when we first call getinstance, the JVM can help us ensure that instance is only created once, and that the memory assigned to instance is initialized, so we don't have to worry about the 3.2 issue. In addition, the method will only use the mutex at the first call, which solves the inefficient problem in 3.1. The last instance is created the first time the Singletoncontainer class is loaded, and the Singletoncontainer class is loaded when the GetInstance method is called, so it also implements lazy loading.
Multithreaded under Singleton mode: Lazy loading (deferred loading) and instant loading