Java implements the singleton pattern of design patterns


The singleton pattern involves a single class that is responsible for creating its own objects while ensuring that only a single object is created. This class provides a way to access its unique object, which can be accessed directly without instantiating objects of this class.

Application scenarios:

  • Since the configuration file is a shared resource, the singleton mode is generally used to read the configuration object.
  • The design of the database connection pool generally also adopts the singleton mode.
  • The design of multi-threaded thread pool generally adopts singleton mode.

1. Hungry Chinese

Hungry Chinese style, after the class is loaded into the memory, a singleton is instantiated. The JVM guarantees thread safety, it is simple and practical, and is recommended. The only disadvantage: whether it is used or not, the instantiation is completed when the class is loaded (in fact, this is not a disadvantage, because it is loaded for the purpose of using it? Otherwise, why do you load it).

public class Singleton {
    
    
    //构造方法私有化,防止在外部被实例化
    private Singleton(){
    
    }
    //静态实例私有化,防止外部引用,直接初始化。
    private static Singleton INSTANCE = new Singleton();
    //提供公开的静态方法,用来在外部获取实例
    public static Singleton getInstance(){
    
    
        return INSTANCE;
    }
}

2. Lazy man

2.1. Single synchronization lock

The performance of this solution is too poor, and the lock must be acquired every time the object is taken, so this method of writing is generally not recommended.

public class Singleton {
    
    
    //构造方法私有化,防止在外部被实例化
    private Singleton(){
    
    }
    //静态实例私有化,防止外部引用
    private static Singleton INSTANCE = null;
    //提供公开的静态方法,用来在外部获取实例
    public static synchronized Singleton getInstance(){
    
    
        if (INSTANCE == null){
    
    
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
}

2.2. Double check lock

DCL( Double Check Lock), double check lock.

public class Singleton {
    
    
    //构造方法私有化,防止在外部被实例化
    private Singleton(){
    
    }
    //静态实例私有化,防止外部引用(volatile 禁止指令重排,保证线程修改可见性)
    private static volatile Singleton INSTANCE = null;
    //提供公开的静态方法,用来在外部获取实例
    public static Singleton getInstance(){
    
    
        if (INSTANCE == null){
    
    //第一重校验
            synchronized (Singleton.class){
    
    //锁定
                if (INSTANCE == null){
    
    //第二重校验
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}

2.2.1. The role of volatile

(1) Ensure the visibility of thread modification
Programs written in Java language, sometimes in order to improve operating efficiency, the compiler will automatically optimize them, and cache frequently accessed variables. When the program reads this variable, it may be directly from the cache ( For example, the register is read instead of the memory. When multi-threaded programming, the value of the variable may be changed by another thread, and the value of the cache will not change accordingly, resulting in inconsistency between the value read and the value of the actual variable.

volatileThe modified member variable is forced to reread the value of the member variable from the shared memory every time it is accessed by the thread. Moreover, when the member variable changes, the thread is forced to write the changed value back to the shared memory. In this way, at any time, two different threads always see the same value of a member variable.

(2) Instruction reordering is prohibited.
In the Java Memory Model (JMM), the processor's instruction order is not restricted. To put it bluntly, the order may be disrupted without affecting the result.

When INSTANCE = new Singleton();this statement is executed , JMM is not executed all at once, that is, it is not atomic. In essence, this command is divided into three parts:

  1. Allocate memory space for the object, and assign default values ​​to the fields. (Application space)
  2. Execute the constructor statement to initialize the instance object. (Initialization object)
  3. INSTANCEPoint the reference to the allocated memory space. (Reference association)

In JMM these three steps 2and 3not necessarily the execution of the order, if the thread Aorder execution is 1, , 3in2 the first 2time step is finished, just thread Bthe implementation of the first sentence empty statement, it will return directly INSTANCE, then At this time, INSTANCEonly inaction is obtained null, and there is no initialization in essence. Such an object must be problematic! (That is to say, the thread Bmay obtain an object in a semi-initialized state, and the value of each field in the object is still the default value of each type, and the initialization of the value has not been completed.)

The volatilesignificance of keywords is to ensure that the execution commands will not be reordered, which avoids the occurrence of this abnormal situation, so this method of obtaining a singleton is truly safe and reliable!

2.2.2. Disadvantages of volatile

The use of volatileshielding eliminates the necessary code optimization in the JVM, so the execution efficiency will be relatively low.

2.3. Static inner class

public class Singleton {
    
    
    //构造方法私有化,防止在外部被实例化
    private Singleton(){
    
    }
    //使用静态内部类维护单例
    private static class SingletonFactory{
    
    
        private static Singleton INSTANCE = new Singleton();
    }
    //提供公开的静态方法,用来在外部获取实例
    public static Singleton getInstance(){
    
    
        return SingletonFactory.INSTANCE;
    }
}

2.4. Enumeration

public class Singleton {
    
    
    //构造方法私有化,防止在外部被实例化
    private Singleton(){
    
    }
    //使用枚举实现单例
    private enum Instance{
    
    
        INSTANCE;
        private Singleton singleton;
        Instance(){
    
    
            singleton = new Singleton();
        }
        public Singleton getSingleton(){
    
    
            return singleton;
        }
    }
    //提供公开的静态方法,用来在外部获取实例
    public static Singleton getInstance(){
    
    
        return Instance.INSTANCE.getSingleton();
    }
}

2.5. CASE

For the above several implementations, the implementation principle is to use the initialization of the singleton when the class is loaded, that is, the thread safety mechanism of the ClassLoader is used. The so-called thread safety mechanism of ClassLoader is that the loadClass method of ClassLoader uses synchronizedkeywords when loading classes . It is precisely because of this that unless rewritten, this method is synchronized throughout the loading process by default, which guarantees thread safety.

Therefore, although some implementations of the above methods are not explicitly used synchronized, the underlying implementation principles are still used synchronized.

Is there a way to implement thread-safe singletons without using locks? Yes, that is use CAS. CASIt is an optimistic locking technology. When multiple threads try to CASupdate the same variable at the same time, only one of the threads can update the value of the variable, and the other threads will fail. The failed thread will not be suspended, but will be notified. Failed in this competition and can try again.

CAS( Compare And Swap) Comparison and replacement is a technique used when designing concurrent algorithms.

public class Singleton {
    
    
    //构造方法私有化,防止在外部被实例化
    private Singleton(){
    
    }
    //使用原子类包装
    private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<>();
    //提供公开的静态方法,用来在外部获取实例
    public static Singleton getInstance(){
    
    
        for (;;){
    
    //死循环,相当于while(true){}
            Singleton singleton = INSTANCE.get();
            if (singleton != null){
    
    
                return singleton;
            }
            singleton = new Singleton();
            //CAS操作,如果INSTANCE为null,则把它修改为singleton
            if (INSTANCE.compareAndSet(null, singleton)){
    
    
                return singleton;
            }
        }
    }
}

Are there any advantages and disadvantages to the singleton implemented in this way?

With CASbenefits that no use of traditional locking mechanism to ensure the security thread, CASa busy wait algorithm based on the underlying hardware implementation-dependent, it is not relative to the lock thread switching and blocking of additional consumption, can support large parallel degree.

CASAn important shortcoming is that if busy waiting has been executed unsuccessfully (always in an infinite loop), it will cause greater execution overhead to the CPU. In addition, if N threads execute at singleton = new Singleton();the same time, a large number of objects will be created, which may cause memory overflow.

Guess you like

Origin blog.csdn.net/wb1046329430/article/details/115260999