Elaborate on Java singleton design pattern

What is design pattern?
The design pattern is like the chess score in our chess game, with the red side as the head gun and the black side as the horse jump. For some walking methods of red hair, there are some fixed routines when the black side goes down. If you follow the routines, the situation will suffer.
Just like you play a game and play a hero, you won't play too badly if you play according to a certain way.

Singleton design pattern is a very common design pattern in design patterns. Singleton pattern can ensure that there is only one instance of a class in the program without creating multiple instances. For example, in JDBC programming, some URL s and usernames of the database are the only instances.
There are two kinds of singleton design modes, one is hungry and the other is lazy

Hungry man model

The hungry man mode is actually called immediate loading, which means urgent and urgent, so it is also called the hungry man mode. Hungry man mode means that objects have been created when using classes.

Starving mode code:

Set the construction method to private to prevent the caller from modifying the instance of the class outside the class. Set the instance to private and set it as the class attribute.
We use static inner classes here

public class TestDemo {
    //Lazy style
    static class Singleton{
        private static Singleton instance = new Singleton();
        //Set the constructor private to prevent creating instances outside the class
        private Singleton() {

        }
        //Only read operations are involved here, and there is no thread safety problem
        public Singleton getInstance() {
            return instance;
        }
    }
}

Lazy mode

Deferred loading, also known as lazy mode, is created when the get() method instance is called.

Take a look at the code:
This code certainly has no problem when it is single threaded, but thread safety problems will certainly occur when it is multi-threaded.

public class TestDemo2 {
    //Lazy style
    static class Singleton {
        private static Singleton instance = null;
        private Singleton() {

        }
        //This involves multiple threads. When multiple threads are involved in modification at the same time, the existing thread will be unsafe
        public static Singleton getInstance() {
            if (instance == null) {
                //If the instance has not been created, when multiple threads are involved, modification will be involved, and thread safety problems will occur
                instance = new Singleton();
            }
            //If the instance has been created, there is no security problem
            return instance;
        }
    }
}

For example, if determines the operation here, suppose we have two threads executing if operation at the same time. Now thread 1 executes the if operation, which is null, and then thread 2 also executes the if operation, which is null.

Both threads enter this if. First, thread 1 executes the new operation, and then thread 2 executes the new operation, overwriting the previous instance. It seems that it has no effect.

For example, in extreme cases, the new object consumes 10G of memory and takes several minutes. Can you do that?

The solution is to make the if and new objects atomic, that is, use synchronized locking. Guaranteed atomicity.


The getInstance() method involves security only when it is called for the first time, so we add a lock here.
Isn't the subsequent call related to the locking operation?
There was no thread safety problem in the follow-up. There is no need to add a lock. Don't you try to add a lock again?
Locking is an expensive operation. Locking where you shouldn't lock here may reduce the speed of this code by many times.

Improvement idea: lock the first call, and do not lock the subsequent calls.

Add an if condition to the outermost layer to distinguish between the first call and the subsequent call.
Layer 1 if: distinguish between the first thread and the subsequent thread, and decide whether to lock
Because there will be blocking waiting in the synchronized lock, and it is not specified how long to wait, it is limited between the outermost if
The inner layer if is accessed only by the first batch of lucky people who first grabbed the lock

When the first batch of threads enter the lock phase through the first layer if and create the object, it is equivalent to changing the value of insertion in memory to non null

When threads in subsequent batches pass through the first layer if, they also need to determine the value of instance, but this determination does not necessarily read data from memory, but may also read data from registers


Do you think it's over? Not really.
Due to the optimization of the compiler, in the first layer of if, threads may access the value in the CPU register. At this time, the value stored in the CPU register may still be null, so the first if may fail.
This is a memory visibility problem,
The solution is very simple. Modify instance in volatile to ensure memory visibility.


Thread safe lazy Code:

public class TestDemo3 {
    //Thread safe lazy
    static class Singleton {
        //volatile is added to ensure memory visibility and prevent compiler optimization. Cause layer 1 if failure
        private volatile static Singleton instance = null;
        private Singleton() {

        }
        public static Singleton getInstance() {
            //This layer prevents too many threads from grabbing locks
            if(instance == null) {
                synchronized (Singleton.class) {
                    //Only a lucky man can get here
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
}

Keywords: Java Design Pattern Back-end Multithreading

Added by roopali on Wed, 10 Nov 2021 14:08:55 +0200