preface

The singleton pattern is one of the simplest Java design patterns. It only takes a single class to implement the singleton pattern. However, you can’t take the singleton pattern seriously.

Definition of a singleton pattern

Singleton pattern is instantiated only once in the program is running, create a globally unique object, a bit like Java static variables, but the singleton pattern is better than static variables, static variables in the program start when the JVM will to load, if you don’t use, will cause a lot of waste of resources, the singleton pattern to implement lazy loading, You can create an instance only when it is being used. Many of the tool classes in the development tool library apply the singleton pattern, proportional thread pool, cache, log object, etc., which only need to create one object. Creating multiple instances may cause unpredictable problems, such as waste of resources, inconsistent result processing, etc.

The idea of singleton implementation

  • Statically instantiate the object
  • Privatize constructors, disallowing instance creation through constructors
  • Provides a public static method that returns a unique instance

Benefits of singletons

  • With only one object, low memory overhead and good performance

  • Avoid multiple occupancy of resources

  • Set up global access points in the system to optimize and share resource access

Implementation of singleton pattern

The singleton pattern method is hungry, lazy, double check the lock mode, singleton pattern, enumeration class implements a static inner class singleton five ways, including idlers, double-checked locking two ways, if you are not written in the in a multi-threaded cases there is not a singleton or singleton out such problems as abnormal, the specific reason, This will be explained in the corresponding section below. We start our singleton writing with the most basic hunger-man pattern.

The hungry mode

The Hanky-hank pattern takes a crude form that instantiates objects directly when defining static properties. The code is as follows:

// Initialization is done at class loading time, so class loading is slow, but object fetching is fast
public class SingletonObject1 {
    // Use static variables to store unique instances
    private static final SingletonObject1 instance = new SingletonObject1();

    // Privatize constructor
    private SingletonObject1(a){
        // There may be many operations inside
    }

    // Provides a public access to the instance
    public static SingletonObject1 getInstance(a){
        returninstance; }}Copy the code

Advantages and disadvantages of hungry man mode

advantages
  • The use of the static keyword guarantees thread-safety at the JVM level by ensuring that all writes to the variable are completed when the variable is referenced
disadvantages
  • Lazy loading cannot be implemented, resulting in wasted space. If an analogy is large, we load the class at initialization, but we do not use the class for a long time, resulting in wasted memory space.

Lazy mode

Lazy mode is a lazy mode in which instances are not created during program initialization, but only when they are used. Lazy mode solves the problem of wasted space caused by hungry mode, but also introduces other problems. Let’s look at lazy mode first

public class SingletonObject2 {
    // When defining a static variable, the instance is not initialized
    private static SingletonObject2 instance;

    // Privatize constructor
    private SingletonObject2(a){}public static SingletonObject2 getInstance(a){
        // If the instance is empty, instantiate the object
        if (instance == null)
            instance = new SingletonObject2();
        returninstance; }}Copy the code

This is the lazy mode implementation, but the above code is not safe in the case of multithreading, because it is not guaranteed to be a singleton mode, there may be multiple instances, the occurrence of multiple instances is caused by the creation of instance objects. So I present the instantiated code separately to analyze why multiple instances occur.

     1   if (instance == null)
     2       instance = new SingletonObject2();
Copy the code

Let’s say that there are two threads that are both in position 1. Since there is no resource protection, both of the instances that can be judged by the two threads are empty and will execute the instantiation code of 2. Therefore, there will be multiple instances.

Based on the above analysis, we know the cause of multiple instances. If we protect resources when creating instances, can we solve the problem of multiple instances? Indeed, we can solve the problem of multiple instances by adding the synchronized keyword to the getInstance() method and making it a protected resource. Add the synchronized keyword to the code below:

public class SingletonObject3 {
    private static SingletonObject3 instance;

    private SingletonObject3(a){}public synchronized static SingletonObject3 getInstance(a){
        /** * Add a class lock, which affects the performance, and serialize the code. ** * Most of our code blocks are read operations, in which case the code thread is safe ** /

        if (instance == null)
            instance = new SingletonObject3();
        returninstance; }}Copy the code

After modification, we solved the problem of multiple instances, but because the synchronized keyword was added and the code was locked, a new problem was introduced. After locking, the program would become serial, and only the thread that grabbed the lock could execute the code block, which would greatly reduce the performance of the system.

The pros and cons of slacker mode

advantages
  • Realize lazy loading, save memory space
disadvantages
  • Without locking, threads are unsafe and multiple instances can occur
  • In the case of locking, serialization of the program will cause serious performance problems for the system

Double-checked lock mode

For the getInstance() method, most of the operations are read operations. Reads are thread-safe, so we don’t have to make every thread hold a lock to call the method. We need to adjust the locking. This gives rise to a new implementation: double-checked lock mode. Here is a singleton implementation block for double-checked lock mode:

public class SingletonObject4 {
    private static SingletonObject4 instance;

    private SingletonObject4(a){}public static SingletonObject4 getInstance(a){

        // If this is empty, return the instance directly without entering the lock grab phase
        if (instance == null)
            synchronized (SingletonObject4.class){
                // Check again if the lock is empty
                if (instance == null){
                    instance = newSingletonObject4(); }}returninstance; }}Copy the code

Double-checked locking mode is a kind of very good singleton implementation pattern, solve the singleton, performance, thread safety problem, the double checking the lock mode looks perfect, but there are problems, in the case of multi-threaded, there might be null pointer, the cause of the problem is the JVM at the time of instantiation objects will reorder operation optimization and instructions. What is order reordering? Take a look at the following example for a brief look at instructions from sort

    private SingletonObject4(a){
     1   int x = 10;
     2   int y = 30;
     3  Object o = new Object();
                
    }
Copy the code

The constructor SingletonObject4() above, which we wrote in order 1, 2, 3, is reordered by the JVM, so the execution order could be 3, 1, 2, or 2, 3, 1. In either order, the JVM eventually guarantees that all instances are instantiated. If there are a lot of operations in the constructor, the JVM will return the object before all the properties in the constructor have been instantiated to improve efficiency. Double check the cause of null pointer problem is appeared in here, when a thread for lock instantiated, other threads can for instance use directly, because of the JVM instruction reordering, other threads to obtain objects may not be a complete object, so when using the instance would be a null pointer exception.

To solve the problem of null-pointer exceptions caused by double-checked locking, use the volatile keyword, which strictly follows the happens-before rule that writes must complete before reads. The singleton pattern code after the volatile keyword is added:

    // Add the volatile keyword
    private static volatile SingletonObject5 instance;

    private SingletonObject5(a){}public static SingletonObject5 getInstance(a){

        if (instance == null)
            synchronized (SingletonObject5.class){
                if (instance == null){
                    instance = newSingletonObject5(); }}returninstance; }}Copy the code

The double-checked locking pattern with the volatile keyword is a good singleton implementation that ensures thread-safety without performance issues in multithreaded situations.

Static inner class singleton pattern

The static inner class singleton mode is also called the singleton holder mode. Instances are created by the inner class. Since the JVM does not load a static inner class during the loading of an external class, the inner class is loaded only when its properties/methods are called and its static properties initialized. Static attributes are modified static and are guaranteed to be instantiated only once, in a strict order. The code for the static inner class singleton pattern is as follows:

public class SingletonObject6 {


    private SingletonObject6(a){}// Singleton holder
    private static class InstanceHolder{
        private  final static SingletonObject6 instance = new SingletonObject6();

    }
    
    // 
    public static SingletonObject6 getInstance(a){
        // Invoke the inner class attribute
        returnInstanceHolder.instance; }}Copy the code

The static inner class singleton pattern is an excellent singleton pattern, which is commonly used in open source projects. In the case of no lock, to ensure the safety of multi-threading, and no performance impact and space waste.

Enumeration classes implement the singleton pattern

The effective singleton implementation is highly recommended by Java authors because enumerations are thread-safe and only load once. The designers took advantage of the nature of enumerations to implement the singleton pattern. And enumerated types are the only singleton implementation pattern in use that can’t be broken.

public class SingletonObject7 {


    private SingletonObject7(a){}/** * Enumeration types are thread-safe and are loaded only once */
    private enum Singleton{
        INSTANCE;

        private final SingletonObject7 instance;

        Singleton(){
            instance = new SingletonObject7();
        }

        private SingletonObject7 getInstance(a){
            returninstance; }}public static SingletonObject7 getInstance(a){

        returnSingleton.INSTANCE.getInstance(); }}Copy the code

Methods and solutions of breaking singleton pattern

1, all methods except enumeration destroy singletons by reflection. Reflection generates new objects by calling the constructor. Therefore, if we want to prevent singletons from being destroyed, we can check in the constructor.

private SingletonObject1(a){
    if(instance ! =null) {throw new RuntimeException("Instance already exists, please get it by getInstance() method"); }}Copy the code

2. If a singleton class implements Serializable, it can be broken by deserialization, so we can not implement the serialization interface. If we must implement the serialization interface, we can override readResolve() to return the related singleton directly.

  public Object readResolve(a) throws ObjectStreamException {
        return instance;
    }
Copy the code

If you think the article is good, welcome to like and forward

The last

Play a small advertisement, welcome to scan the code to pay attention to the wechat public number: “The technical blog of the flathead brother”, progress together.