Java singleton pattern

Reprinted from http://blog.csdn.net/goodlixueyong/article/details/51935526

The Singleton pattern in Java is a widely used design pattern. The main function of the singleton pattern is to ensure that only one instance of a class exists in a Java program. Some managers and controllers are often designed as singletons.

The singleton pattern has many advantages. It can avoid repeated creation of instance objects, not only can reduce the time overhead of creating objects each time, but also save memory space; it can avoid logical errors caused by operating multiple instances. If an object has the potential to run through the entire application, and play the role of global unified management control, then the singleton pattern may be an option worth considering.

There are many ways to write the singleton pattern, and most of them have more or less shortcomings. These writing methods will be introduced separately below.

1. Hungry man mode

public class Singleton{  
    private static Singleton instance = new Singleton();  
    private Singleton(){}  
    public static Singleton newInstance(){  
        return instance;  
    }  
} 

From the code, we can see that the constructor of the class is defined as private to ensure that other classes cannot instantiate this class, and then provides a static instance and returns it to the caller. Hungry mode is the easiest way to implement it. Hunger mode creates instances when classes are loaded, and instances exist throughout the program cycle. Its advantage is that the instance is only created once when the class is loaded, and there is no situation where multiple threads create multiple instances, avoiding the problem of multi-thread synchronization. Its shortcomings are also obvious, even if the singleton is not used, it will be created, and it will be created after the class is loaded, and the memory will be wasted.
This implementation is suitable for the case where the singleton occupies a small amount of memory and will be used during initialization. However, if the memory occupied by the singleton is relatively large, or the singleton is only used in a specific scenario, it is not suitable to use the hungry mode. In this case, you need to use the lazy mode for lazy loading.

2, lazy mode

public class Singleton{  
    private static Singleton instance = null;  
    private Singleton(){}  
    public static Singleton newInstance(){  
        if(null == instance){  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}  

In the lazy mode, the singleton is created when it is needed. If the singleton has been created, calling the get interface again will not recreate the new object, but directly return the previously created object. If a singleton is used less frequently, and creating a singleton consumes a lot of resources, then it is necessary to create a singleton on demand. At this time, using the lazy mode is a good choice. However, the lazy mode here does not consider the issue of thread safety. Multiple threads may call its getInstance() method concurrently, resulting in the creation of multiple instances. Therefore, it is necessary to lock to solve the problem of thread synchronization. The implementation is as follows.

public class Singleton{  
    private static Singleton instance = null;  
    private Singleton(){}  
    public static synchronized Singleton newInstance(){  
        if(null == instance){  
            instance = new Singleton();  
        }  
        return instance;  
    }  
} 

3. Double check lock

The locked lazy mode seems to solve the problem of thread concurrency and realize lazy loading. However, it has performance problems and is still not perfect. The synchronized method modified by synchronized is much slower than the general method. If getInstance() is called multiple times, the accumulated performance loss will be relatively large. Therefore, there is a double check lock, let's first look at its implementation code.

public class Singleton {  
    private static Singleton instance = null;  
    private Singleton(){}  
    public static Singleton getInstance() {  
        if (instance == null) {  
            synchronized (Singleton.class) {  
                if (instance == null) {//2  
                    instance = new Singleton();  
                }  
            }  
        }  
        return instance;  
    }  
} 

It can be seen that there is an additional layer of judgment that the instance is empty outside the synchronization code block. Since the singleton object only needs to be created once, if you call getInstance() again later, you only need to return the singleton object directly. Therefore, in most cases, calling getInstance() will not execute to the synchronized code block, thereby improving program performance. However, there is another situation to consider. If two threads A, B, and A execute the if (instance == null) statement, it will think that the singleton object has not been created. At this time, the thread switches to B and executes the same statement. B also thinks that the singleton object is not created, and then the two threads execute the synchronized code block in turn, and create a singleton object respectively. In order to solve this problem, it is also necessary to add an if (instance == null) statement to the synchronized code block, which is the code 2 seen above.
We see that the double check lock realizes delayed loading, solves the problem of thread concurrency, and also solves the problem of execution efficiency. Is it really foolproof?

Here to mention the instruction rearrangement optimization in Java. The so-called instruction rearrangement optimization refers to adjusting the execution order of instructions to make the program run faster without changing the original semantics. The JVM does not specify the content related to compiler optimization, that is to say, the JVM is free to optimize instruction reordering.

The key to this problem is that due to the existence of instruction rearrangement optimization, the order of initializing Singleton and assigning the object address to the instance field is uncertain. When a thread creates a singleton object, memory space is allocated for the object and the object's fields are set to default values ​​before the constructor is called. At this point, the allocated memory address can be assigned to the instance field, but the object may not have been initialized yet. If another thread immediately calls getInstance, it will get an object with an incorrect state, and the program will fail.

The above is the reason why the double check lock will fail, but fortunately, the volatile keyword has been added in JDK1.5 and later versions. One of the semantics of volatile is to prohibit instruction reordering optimization, which ensures that the object is already initialized when the instance variable is assigned, thus avoiding the above-mentioned problems. code show as below:

public class Singleton {  
    private static volatile Singleton instance = null;  
    private Singleton(){}  
    public static Singleton getInstance() {  
        if (instance == null) {  
            synchronized (Singleton.class) {  
                if (instance == null) {  
                    instance = new Singleton();  
                }  
            }  
        }  
        return instance;  
    }  
} 

4. Static inner class

In addition to the above three ways, there is another way to implement singleton, through static inner class. First look at its implementation code:

public class Singleton{  
    private static class SingletonHolder{  
        public static Singleton instance = new Singleton();  
    }  
    private Singleton(){}  
    public static Singleton newInstance(){  
        return SingletonHolder.instance;  
    }  
}  

This method also uses the class loading mechanism to ensure that only one instance instance is created. Like the hungry man mode, it also uses the class loading mechanism, so there is no problem of multi-thread concurrency. The difference is that it creates object instances in inner classes. In this way, as long as the inner class is not used in the application, the JVM will not load the singleton class and will not create the singleton object, thus realizing lazy lazy loading. That is to say, this method can guarantee lazy loading and thread safety at the same time.

5. Enumeration

Let's look at the last implementation method to be introduced in this article: enumeration.

public enum Singleton{  
    instance;  
    public void whateverMethod(){}      
} 

The four ways of implementing singletons mentioned above all share common disadvantages:

1) Additional work is required to implement serialization, otherwise a new instance will be created every time a serialized object is deserialized.

2) You can use reflection to force the call to the private constructor (if you want to avoid this, you can modify the constructor to throw an exception when the second instance is created).

The enumeration class solves these two problems very well. In addition to thread safety and preventing reflection to call the constructor, enumeration also provides an automatic serialization mechanism to prevent the creation of new objects during deserialization. Therefore, the method recommended by the authors of Effective Java. However, in actual work, it is rare to see people write this way.

Summarize

This article summarizes five methods for implementing singletons in Java. The first two are not perfect. The methods of double-checking locks and static inner classes can solve most problems. These two methods are most commonly used in work. Although the enumeration method perfectly solves various problems, this way of writing is somewhat unfamiliar. My personal suggestion is to use the third and fourth ways to implement the singleton pattern without special needs.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325385680&siteId=291194637