[Java|Multi-threading and high concurrency] design pattern - singleton mode (hungry style, lazy style and static inner class)

1 Introduction

A design pattern is a methodology commonly used in software development to solve complex problems. It provides a proven set of solutions for the design and implementation of specific types of problems. Design patterns help developers improve code reusability, maintainability, and scalability.

There are many design patterns, this article mainly introduces the singleton pattern.
insert image description here

2. Singleton mode

The singleton pattern is a creational design pattern that guarantees that a class has only one instance and provides a global access point to obtain that instance.

3. How to ensure that a class has only one instance

In Java, statickeywords are often used to ensure that a class has only one instance.

In the singleton pattern, 类的构造函数被私有化, to prevent external code from directly creating instances. Then, get a unique instance of the class through a static method or static variable .

If the instance does not exist, a new instance is created and returned; if the instance already exists, the instance is returned directly.

Instead, there are many ways to achieve the above. Here are three common implementation modes:

  1. Hungry Chinese style
  2. Lazy
  3. static inner class

4. Hungry Chinese style singleton mode

Hungry-style singleton: Create an instance when the class is loaded to ensure that there is an instance available under any circumstances.

Code example:

public class Singleton {
    
    
    private static final Singleton instance = new Singleton();
    
    private Singleton() {
    
    
		// 私有化构造函数
    }

    public static Singleton getInstance() {
    
    
        return instance;
    }
}

Code analysis:
insert image description here

The privatized constructor makes it impossible for the outside to directly create an instance. If other threads want to get Singletonan instance of the class, they can only get it through the method Singletonprovided by the class getInstance().

5. Lazy singleton pattern

Lazy Singleton: Lazily load instances and create instances only when needed.

Code example:

public class SingletonLazy {
    
    
    private static SingletonLazy instance = null;
    private SingletonLazy(){
    
    
		// 私有化构造方法
    }
    public static SingletonLazy getInstance(){
    
    
        if (instance == null){
    
    
            instance = new SingletonLazy();
        }
        return instance;
    }
}

Code analysis:
insert image description here

If the method is not called in the subsequent code getInstance, then the step of creating an instance is saved.

In the above code, which way to implement the singleton mode is thread-safe?

The answer is the hungry man mode

Hungry mode does not involve modification, while lazy mode involves reading and modification. Then it is not safe in multi-threaded mode.

Imagine a scenario, if in lazy mode, two threads call the getInstance method at the same time, if one thread reads the instance is null, and creates an instance for the instance, while the instance read by the other thread is still null, and then An instance is created for the instance. Then the instance is created multiple times

6. Implement thread-safe Lazy Singletons

Since the lazy man mode is not safe in a multi-threaded environment, what if the lazy man mode is guaranteed to be safe in a multi-threaded environment?

Since it involves multi-threading, it is inseparable from "lock", that issynchronized

Example:
insert image description here
Through locking, the thread safety problem is solved , but it brings new problems.

Lazy singletons are only getInstancethread-unsafe when the method is called for the first time. Once created, they are thread-safe.

However, the above method of locking will cause the thread to still need to be locked when it is already safe. 有些多此一举了And the cost of locking is relatively large, which affects efficiency.

So what if there is no lock when thread safety is guaranteed?

Before the instance is created, the thread is not safe. After the instance is created, the thread is safe. Then a judgment can be made before the locking operation.

public class SingletonLazy {
    
    
    private static SingletonLazy instance = null;
    private SingletonLazy(){
    
    

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

Code analysis:
insert image description here

Use 双重ifto ensure that the instance is created only once and that it is not locked when the thread is safe. Here we need to focus on understanding.

But thinking carefully about the above code there is still a problem .

If two threads call getInstancethe method at the same time, the first thread gets the lock, enters the second layer if, and starts to create new objects.

The new operation here can be divided into:

  1. Apply for memory and get the first address of memory
  2. Call the constructor to initialize the instance
  3. Assign the first address of the memory to the instance reference

For the above-mentioned new operation, the compiler may perform "instruction reordering". This may cause 无序写入problems

The order of 2 and 3 in the above steps can be changed in a single-threaded environment without affecting the result.

But it can cause problems in a multi-threaded environment 无序写入:

  1. When thread A executes the instance = new Singleton(); line of code, due to the reordering of instructions, it may first perform any combination of the three operations of allocating memory space, initializing instance objects, and pointing instance to memory space, instead of following the code Execute sequentially.
  2. Assuming that thread A has finished allocating memory space and initializing instance objects, but has not pointed instance to the memory space, thread B executes the first instance == null judgment at this time, and the result is false, and returns instance directly. But the instance is not properly initialized at this time.

In order to solve the above problems, keywords can be used volatile. It can prohibit instruction reordering optimization and ensure that the write operation of the instance precedes the read operation, thereby preventing other threads from obtaining the instance when the instance is not properly initialized.

public class SingletonLazy {
    
    
    private static volatile SingletonLazy instance = null;
    private SingletonLazy(){
    
    

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

The above is a completely safe way of writing a lazy singleton mode in a multi-threaded environment.

7. Static inner class implements singleton pattern

In addition to the above methods, the singleton mode can also be implemented using static inner classes.

This method uses the characteristics of static inner classes to achieve lazy loading and thread safety.

Code example:

public class Singleton {
    
    
    private Singleton() {
    
    
        // 私有化构造函数
    }
    
    private static class SingletonHolder {
    
    
        private static final Singleton instance = new Singleton();
    }
    
    public static Singleton getInstance() {
    
    
        return SingletonHolder.instance;
    }
}

The static inner class SingletonHolder holds the only instance of Singleton. When the getInstance method is called for the first time, the initialization of SingletonHolder is triggered to create an instance.

Since the initialization of the static inner class is thread-safe, it can be ensured that only one instance is created in a multi-threaded environment.

8. Summary

This article mainly introduces three ways to realize the singleton mode, the hungry style, the lazy style and the static inner class. Among them, the lazy style singleton is very important, and we must focus on understanding the meaning of double if.

It should be noted that the above methods can ensure the correct creation of singletons in a multi-threaded environment, but in special cases, such as using mechanisms such as reflection or serialization/deserialization, the uniqueness of singletons may still be destroyed.

In practical applications, it is necessary to select an appropriate implementation of the singleton mode according to specific scenarios and requirements.

insert image description here

Thank you for watching! I hope this article can help you!
Column: "Java Learning Journey from Scratch" is constantly being updated, welcome to subscribe!
"Wish to encourage you and work together!"
insert image description here

Guess you like

Origin blog.csdn.net/m0_63463510/article/details/131349717