Singleton Pattern of java design pattern (Singleton Pattern)

concept

The singleton pattern ( Singleton Pattern) is one of the simplest design patterns in Java. This type of design pattern is a creational pattern. The definition given in the [GOF book][3] is: Ensure that a class has only one instance and provide a global access point to it.

The singleton pattern is generally reflected in the class declaration. The singleton class is responsible for creating its own objects while ensuring that only a single object is created. This class provides a way to access its only object, directly, without instantiating an object of this class.

use

The singleton pattern has the following two advantages:

There is only one instance in the memory, which reduces the memory overhead, especially the frequent creation and destruction of instances (such as website homepage page cache).

Avoid multiple occupations of resources (such as writing file operations).

Sometimes, when we choose to use the singleton pattern, we not only consider the advantages it brings, but also may have a singleton in some scenarios. For example, a situation like "a party can only have one chairman".

Method to realize

We know that the generation of an object of a class is done by the class constructor. If a class provides publica constructor to the outside world, then the outside world can create objects of this class arbitrarily. Therefore, if you want to limit the generation of objects, one way is to make the constructor private (at least protected), so that external classes cannot generate objects by reference. At the same time, in order to ensure the availability of the class, it is necessary to provide an object of its own and a static method to access this object.

[QQ20160406-0][4]

Hungry Chinese style

Here is a simple singleton implementation:

//code 1
public class Singleton {
    //在类内部实例化一个实例
    private static Singleton instance = new Singleton();
    //私有的构造函数,外部无法访问
    private Singleton() {
    }
    //对外提供获取实例的静态方法
    public static Singleton getInstance() {
        return instance;
    }
}

Test with the following code:

//code2
public class SingletonClient {

    public static void main(String[] args) {
        SimpleSingleton simpleSingleton1 = SimpleSingleton.getInstance();
        SimpleSingleton simpleSingleton2 = SimpleSingleton.getInstance();
        System.out.println(simpleSingleton1==simpleSingleton2);
    }
}

Output result:

true

Code 1 is a simple singleton implementation, which we call the Hungry Chinese style. The so-called hungry man. This is a relatively vivid metaphor. For a hungry man, he hopes that when he wants to use this instance, he can get it immediately without any waiting time. Therefore, through staticthe static initialization method, when the class is loaded for the first time, an SimpleSingletoninstance of it is created. This ensures that the object is already initialized the first time you want to use it.

At the same time, since the instance is created when the class is loaded, thread safety issues are also avoided. (For the reason, see: [In-depth analysis of Java's ClassLoader mechanism (source level)][5], [Java class loading, linking and initialization][6])

There is also a variant of the hungry man mode:

//code 3
public class Singleton2 {
    //在类内部定义
    private static Singleton2 instance;
    static {
        //实例化该实例
        instance = new Singleton2();
    }
    //私有的构造函数,外部无法访问
    private Singleton2() {
    }
    //对外提供获取实例的静态方法
    public static Singleton2 getInstance() {
        return instance;
    }
}

Code 3 and code 1 are actually the same, they both instantiate an object when the class is loaded.

Hungry Chinese singleton, the object will be instantiated when the class is loaded. This may cause unnecessary consumption, because it is possible that this instance will not be used at all. Moreover, if this class is loaded multiple times, it will also cause multiple instantiations. In fact, there are many ways to solve this problem. Two solutions are provided below. The first one is to use static inner classes. The second is to use the lazy style.

static inner class

Let's first look at solving the above problems through static inner classes:

//code 4
public class StaticInnerClassSingleton {
    //在静态内部类中初始化实例对象
    private static class SingletonHolder {
        private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
    }
    //私有的构造方法
    private StaticInnerClassSingleton() {
    }
    //对外提供获取实例的静态方法
    public static final StaticInnerClassSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

This method also uses the mechanism of classloder to ensure that there instanceis only one thread during initialization. It is different from the hungry Chinese style (very subtle difference): as long as the hungry Chinese style Singletonis loaded, instanceit will be instantiated (no To achieve lazy loading effect), and this way is that Singletonthe class is loaded, instancenot necessarily initialized. Because SingletonHolderthe class is not actively used, the loading class is displayed and instantiated only getInstancewhen the method is called . Imagine that if instantiation consumes resources, I want it to be loaded lazily. On the other hand, I don't want to instantiate it when the class is loaded, because I can't ensure that the class may be actively used in other places to be loaded. Then instantiation is obviously inappropriate at this time. At this time, this method is more reasonable than the hungry Chinese style.SingletonHolderinstanceinstanceSingletonSingletoninstance

Lazy

Let's look at another singleton mode that is instantiated when the object is actually used-the lazy man mode.

//code 5
public class Singleton {
    //定义实例
    private static Singleton instance;
    //私有构造方法
    private Singleton(){}
    //对外提供获取实例的静态方法
    public static Singleton getInstance() {
        //在对象被使用的时候才实例化
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

The above singleton is called lazy singleton. Lazy people just don't create instances in advance and delay the instantiation of the class to itself until the first time it is referenced. getInstanceThe function of the method is to hope that the object will be released when it is used for the first time new.

Have you noticed that there is actually a problem with lazy singletons like code 5, and that is thread safety. In the case of multithreading, it is possible for two threads to enter ifthe statement at the same time, so that two different objects are created when both threads exit from the if. (I won’t explain it in detail here, please make up for multi-threading knowledge if you don’t understand).

thread-safe lazy

For thread-unsafe lazy-style singletons, the solution is actually very simple, which is to lock the steps of creating objects:

//code 6
public class SynchronizedSingleton {
    //定义实例
    private static SynchronizedSingleton instance;
    //私有构造方法
    private SynchronizedSingleton(){}
    //对外提供获取实例的静态方法,对该方法加锁
    public static synchronized SynchronizedSingleton getInstance() {
        //在对象被使用的时候才实例化
        if (instance == null) {
            instance = new SynchronizedSingleton();
        }
        return instance;
    }
}

This way of writing can work well in multithreading, and it seems that it also has a good lazy loading, but, unfortunately, it is very inefficient, because 99% of the cases do not need synchronization. (Because the above synchronizedlocking scope is the entire method, all operations of this method are performed synchronously, but for the case of not creating an object for the first time, that is, the case of not entering the statement, there is no need for ifsynchronous operation at all, you can return directly instance.)

double check lock

In view of the problems existing in code 6 above, I believe that students who understand concurrent programming know how to solve them. In fact, the problem with the above code is that the scope of the lock is too large. Just narrow down the scope of the lock. So how to narrow the scope of the lock? Synchronized code blocks have a smaller locking scope than synchronized methods. code 6 can be transformed into:

//code 7
public class Singleton {

    private static Singleton singleton;

    private Singleton() {
    }

    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

Code 7 is an improved way of writing code 6, which reduces the scope of locks by using synchronized code blocks. This can greatly improve efficiency. (For the existing singletonsituation, there is no need to synchronize, just return directly).

But is it so easy? The above code looks like there is nothing wrong with it. The lazy initialization is realized, the synchronization problem is solved, the scope of the lock is reduced, and the efficiency is improved. However, there are also hidden dangers in this code. The hidden dangers are mainly related to [Java Memory Model (JMM][7]). Consider the following sequence of events:

Thread A finds that the variable has not been initialized, then it acquires the lock and starts the initialization of the variable.

Due to the semantics of some programming languages, the code generated by the compiler allows the variable to be updated and point to a partially initialized object before thread A has finished performing the initialization of the variable.

Thread B finds that the shared variable has been initialized and returns the variable. Since thread B is sure that the variable has been initialized, it does not acquire the lock. If the shared variable is visible to B before A completes its initialization (either because A has not completed its initialization or because some initialized values ​​have not yet crossed the memory used by B (cache coherency)), the program is likely to crash.

(Students who do not understand the above example, please supplement the knowledge about the JAVA memory model)

Using double-checked locks in [J2SE 1.4][8] or earlier is potentially dangerous and sometimes works correctly (distinguishing a correct implementation from a buggy implementation is difficult. Depends on compiler, scheduling of threads and other concurrent system activities, abnormal results from incorrectly implemented double-checked locking may occur intermittently. Reproducing the exception is very difficult.) In [J2SE 5.0][8], this problem was fixed. The [volatile][9] keyword ensures that multiple threads can correctly handle singleton instances

Therefore, for code 7, there are two alternatives, code 8 and code 9:

usevolatile

//code 8
public class VolatileSingleton {
    private static volatile VolatileSingleton singleton;

    private VolatileSingleton() {
    }

    public static VolatileSingleton getSingleton() {
        if (singleton == null) {
            synchronized (VolatileSingleton.class) {
                if (singleton == null) {
                    singleton = new VolatileSingleton();
                }
            }
        }
        return singleton;
    }
}

**The above double check lock method is widely used, and it solves all the problems mentioned above. **However, even this seemingly flawless approach may have problems, and that is when it comes to serialization. The details will be introduced later.

usefinal

//code 9
class FinalWrapper<T> {
    public final T value;

    public FinalWrapper(T value) {
        this.value = value;
    }
}

public class FinalSingleton {
    private FinalWrapper<FinalSingleton> helperWrapper = null;

    public FinalSingleton getHelper() {
        FinalWrapper<FinalSingleton> wrapper = helperWrapper;

        if (wrapper == null) {
            synchronized (this) {
                if (helperWrapper == null) {
                    helperWrapper = new FinalWrapper<FinalSingleton>(new FinalSingleton());
                }
                wrapper = helperWrapper;
            }
        }
        return wrapper.value;
    }
}

Enumeration

Before 1.5, there are generally only the above methods to implement singletons. After 1.5, there is another way to implement singletons, which is to use enumeration:

// code 10
public enum  Singleton {

    INSTANCE;
    Singleton() {
    }
}

This method is advocated by the author of [Effective Java][10] Josh Bloch. It can not only avoid multi-thread synchronization problems, but also prevent deserialization from recreating new objects (described below), which can be described as a very strong barrier. , In the in-depth analysis of Java enumeration types --- enumeration thread safety and serialization issues, there are detailed introductions to enumeration thread safety issues and serialization issues. However, I personally think that the feature was only added in 1.5. Using enumthis The method of writing inevitably makes people feel unfamiliar. In actual work, I rarely see someone writing like this, but it does not mean that he is not good.

Singletons and serialization

In the article [Things about singletons and serialization][11], [Hollis][12] analyzed the relationship between singletons and serialization before serialization can destroy singletons. To prevent serialization from destroying the singleton, just Singletondefine it in the class readResolveto solve the problem:

//code 11
package com.hollis;
import java.io.Serializable;
/**
 * Created by hollis on 16/2/5.
 * 使用双重校验锁方式实现单例
 */
public class Singleton implements Serializable{
    private volatile static Singleton singleton;
    private Singleton (){}
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

    private Object readResolve() {
        return singleton;
    }
}

Summarize

This article introduces several methods to realize singleton, mainly including hungry man, lazy man, using static inner class, double check lock, enumeration, etc. It also covers how to prevent serialization from breaking the singleton of a class.

From the singleton implementation, we can find that a simple singleton pattern can involve so much knowledge. In the process of continuous improvement, more knowledge can be understood and applied. The so-called learning is endless.

Guess you like

Origin blog.csdn.net/zy_dreamer/article/details/132364359