For the first time to get my latest articles, please follow my official account:Technical team

The singleton pattern is one of the easiest design patterns to understand and the easiest to write code by hand. But there are many pits among them, so they are often used as interview questions to test. In this paper, several singleton writing methods are sorted out and their advantages and disadvantages are analyzed. Many of these are old school questions, but if you don’t know how to create a thread-safe singleton or what double-checked locking is, this article may help you.

Lazy threads are not safe

When asked to implement a singleton pattern, many people’s first reaction is to write the following code, including that taught in textbooks.

public class Singleton {
    private static Singleton instance;
    private Singleton (){}
    public static Singleton getInstance() {
     if (instance == null) {
         instance = new Singleton();
     }
     returninstance; }}Copy the code

This code is straightforward and uses lazy loading mode, but it has fatal problems. When multiple threads call getInstance() in parallel, multiple instances are created. That is to say, it will not work in multithreading.

Lazy, thread-safe

To solve the above problem, the easiest way is to set the entire getInstance() method to synchronized.

public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } is thread-safe and solves the multi-instance problem, but it is not efficient. Because only one thread can call the getInstance() method at any time. But the synchronous operation is only needed for the first invocation, when the singleton object is first created. This brings us to the double check lock.

Double check lock

The double Checked locking pattern is a method of locking with synchronous blocks. Programmers call this a double-checked lock because instance == NULL is checked twice, once outside and once inside the synchronized block. Why check again inside the synchronized block? Because it is possible for multiple threads to enter an IF outside the synchronized block, multiple instances will be generated if no second check is performed inside the synchronized block.

public static Singleton getSingleton() {
    if (instance == null) {                         //Single Checked
        synchronized (Singleton.class) {
            if(instance == null) { //Double Checked instance = new Singleton(); }}}return instance ;
}Copy the code

This code looks perfect, but unfortunately, it is flawed. In particular, instance = new Singleton(), which is not an atomic operation, actually does three things in the JVM.

  1. Allocate memory to instance
  2. Call the constructor of the Singleton to initialize a member variable
  3. Reference instance to the allocated memory space (instance is not null after this step)

But there is an optimization for instruction reordering in the JVM’s just-in-time compiler. That is to say, the order of step 2 and step 3 above is not guaranteed, and the final execution order may be 1-2-3 or 1-3-2. If it is the latter, instance is preempted by thread 2 before instance 3 is executed and instance 2 is already non-null (but not initialized), so thread 2 returns instance directly, uses it, and then naturally reports an error.

We simply declare the instance variable as volatile.

public class Singleton { private volatile static Singleton instance; // Declare as volatile privateSingleton (){}
    public static Singleton getSingleton() {
        if (instance == null) {                         
            synchronized (Singleton.class) {
                if(instance == null) { instance = new Singleton(); }}}returninstance; }}Copy the code

Some people argue that the reason for using volatile is visibility, that is, the ability to ensure that a thread does not have a local copy of an instance, instead fetching it from main memory each time. But that’s not true. The main reason for using volatile is another feature: it disallows instruction reordering optimizations. That is, there is a memory barrier (in the generated assembly code) behind assignments to volatile variables, and reads are not reordered to the barrier. For example, the fetch operation must be performed after 1-2-3 or 1-3-2. There is no case where the fetch operation is performed after 1-3. In terms of the “antecedent principle,” writes to a volatile variable occur first (in chronological order) before reads to it.

Note, however, that prior to Java 5, the use of volatile double-checking was problematic. The reason for this was that the JMM (Java Memory Model) prior to Java 5 was flawed, and even declaring variables volatile did not completely avoid reordering, primarily because the code before and after volatile variables still had reordering problems. The problem of volatile shielding reordering was only fixed in Java 5, so it was safe to use volatile after that.

You may not like this complicated and implicit approach, but there are better ways to implement thread-safe singleton patterns.

Static final field

This approach is very simple because instances of singletons are declared as static and final variables and are initialized when the class is first loaded into memory, so creating the instance itself is thread-safe.

Private static final Singleton instance = new Singleton(); privateSingleton(){}
    public static Singleton getInstance() {returninstance; }}Copy the code

If this were perfect, there would be no need to talk about double-checking. The disadvantage is that it is not a lazy initialization. The singleton is initialized as soon as the class is loaded, even if the client does not call the getInstance() method. Hungry type way of creation in some scenes will not be able to use: such as the creation of a Singleton instance is depend on the parameters or configuration file, the getInstance () method must be called before a set parameters to it, as the single example of writing can’t use.

Static inner class

I prefer to use the static inner class approach, which is recommended in Effective Java.

public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        returnSingletonHolder.INSTANCE; }}Copy the code

This approach still uses the JVM’s own mechanism to ensure thread-safety issues; Because SingletonHolder is private, there is no way to access it other than getInstance(), so it is lazy; There is no synchronization when reading instances at the same time, no performance defects; Nor does it depend on the JDK version.

Enumeration Enum

It’s so easy to write singletons using enumerations! This is also its greatest strength. The following code is a common way to declare an enumeration instance.

public enum EasySingleton{
    INSTANCE;
}Copy the code

We can access the INSTANCE via easysingleton.instance, which is much simpler than calling getInstance(). Creating enumerations is thread-safe by default, so you don’t need to worry about double Checked locking, and it also prevents deserialization from causing new objects to be recreated. But it’s still rare to see someone write like this, probably because they’re not familiar with it.

conclusion

In general, there are five ways to write the singleton pattern: slacker, hungry, double-checked lock, static inner class, and enumeration. All of the above are thread-safe implementations, and the first method given at the beginning of this article is not the correct way to write it.

As far as I’m concerned, in general it is good to use the hungry type directly, if clear requirement for lazy loading (lazy initialization) will tend to use a static inner class, if it involves deserialized object creation will try to use the way of enumeration to implement the singleton.

For the first time to get my latest articles, please follow my official account:Technical team