“This is the 15th day of my participation in the First Challenge 2022. For details: First Challenge 2022”

A concept,

The singleton pattern is one of the most widely used design patterns. When applying this pattern, the class of the singleton pattern must ensure that only one instance exists. Most commonly used for classes that require only one instance of the entire program and are usually resource-intensive, such as thread pools, caches, network requests, IO operations, database access, etc. Because a class is expensive, there is no need for it to construct multiple instances, which is a good use of the singleton pattern.

1.1 the singleton class

Singleton Pattern: A class with one and only instance that is self-instantiated to provide to the entire system. Also called a Singleton class.

The singleton pattern has three main points:

  • 1. A class can have only one instance.

  • 2. You must create this instance yourself.

  • 3. This instance must be provided to all other objects.

From the point of view of concrete implementation, it is the following:

  • 1. Singleton classes provide only private constructors.

  • 2. Return a singleton class object via a static method or enumeration.

  • 3. Ensure that the singleton class has one and only one static private object, especially in multithreaded environments.

  • 4. Provides a static public function to create or get its own static private object.

  • 5. Ensure that singleton class objects are not rebuilt when deserialized.

The internal implementation of the singleton class generates only one instance, and it provides a static getInstance() factory method that allows customers to access its unique instance; To prevent instantiation externally, its constructor is designed to be private; A static object of type Singleton is defined inside the Singleton class as a unique instance of external sharing.

1.2 the advantages and disadvantages

1.2.1 advantages

  • 1. The singleton pattern provides controlled access to a unique instance. Because a singleton class encapsulates its unique instance, it has tight control over how and when customers access it.

  • 2. Because there is only one object in the system memory, system resources can be saved. For some objects that need to be created and destroyed frequently, the singleton mode can undoubtedly improve the system performance.

  • 3. Allow a variable number of instances. Based on the singleton pattern, we can extend it to obtain a specified number of object instances using a method similar to singleton control, which not only saves system resources, but also solves the problem that too much singleton object sharing will damage performance.

1.2.2 shortcomings

  • 1. Because there is no abstraction layer in the singleton pattern, it is very difficult to extend the singleton class.

  • 2. The singleton class is too heavy, which violates the “single responsibility principle” to some extent. Because the singleton class acts both as a factory, providing factory methods, and as a product, containing business methods that blend the creation of the product with the functionality of the product itself.

  • 3. Now many object-oriented languages (such as Java, c #) of the running environment provides automatic garbage collection technology, therefore, if the instantiation of the Shared object will not be used for a long time, the system will think that it is junk, automatically destroyed and recycle resources, and the next time you use will instantiate again, this will lead to the loss of Shared state of singleton.

Method of creating singleton pattern

2.1 the hungry

If you are hungry, you are in a hurry to create an object instance. If you are hungry, you create an object instance when you load the class.

This approach is very simple because instances of singletons are declared as static and final variables and are initialized when the class is first loaded into memory, so creating the instance itself is thread-safe.

public class SingletonHungry {
    // Class is initialized when it is loaded
    private static final SingletonHungry singleton = new SingletonHungry();
    private SingletonHungry(a){}
    public static SingletonHungry getInstance(a){
        returnsingleton; }}Copy the code

The disadvantage is that it is not a lazy loading mode, even if the client does not call the getInstance() method, the singleton is initialized as soon as the class is loaded.

Hungry type way of creation in some scenes will not be able to use: such as the creation of a Singleton instance is depend on the parameters or configuration file, the getInstance () method must be called before a set parameters to it, as the single example of writing can’t use.

2.2 LanHanShi

Emphasize laziness, so there is no rush to create object instances when they need to be created. So no object instance is created when an object is loaded.

2.2.1 Lazy (Non-thread-safe)

public class SingletonLazy {
    private static SingletonLazy singletonLazy;
    private SingletonLazy(a){}
    public static SingletonLazy getInstance(a){
        if (singletonLazy == null) {
            singletonLazy = new SingletonLazy();
        }
        returnsingletonLazy; }}Copy the code

This generation uses lazy loading mode, but it has a fatal problem. When multiple threads call getInstance() in parallel, multiple instances are created. That is to say, it will not work in multithreading. So what’s the solution? The simplest way to do this is to add a synchronized to the getInstance() method.

2.2.2 Lazy (Thread-safe)

public class SingletonLazy {
    private static SingletonLazy singletonLazy;
    private SingletonLazy(a){}
    public static synchronized SingletonLazy getInstance(a){
        if (singletonLazy == null) {
            singletonLazy = new SingletonLazy();
        }
        returnsingletonLazy; }}Copy the code

Above, getInstance() is a synchronized method by adding the synchronized keyword, ensuring uniqueness of singleton objects in multithreaded situations.

It is thread-safe and solves the multi-instance problem, but it is not efficient. Because only one thread can call the getInstance() method at any time. But the synchronous operation is only needed for the first invocation, when the singleton object is first created. This brings us to the double check lock.

2.3 Double check lock

The double Checked locking pattern is a method of locking with synchronous blocks. Programmers call it double-checked locking, and it’s one of the most frequently used methods on the Web after all.

Why is it called double-checked lock? Because instance == NULL is checked twice, once outside and once inside the synchronized block. Why check again inside the synchronized block? Because multiple threads may enter if outside the synchronized block at once, unnecessary synchronization is avoided. Avoid generating multiple instances if you do not double check within the synchronized block.

public class SingletonDCL {
    private static SingletonDCL singleton;
    private SingletonDCL(a){}
    public static SingletonDCL getInstance(a){
        if (singleton == null) {
            synchronized (SingletonDCL.class){
                if (singleton == null) {
                    singleton = newSingletonDCL(); }}}returnsingleton; }}Copy the code

This code looks perfect with double checking, but unfortunately, it’s broken. Singleton = new SingletonDCL(). In fact, this statement does roughly three things in the JVM:

  • 1. Allocate memory for singleton.

  • 2. Call the constructor of SingletonDCL to initialize the member variable.

  • 3. Point the Singleton object to the allocated memory space (the singleton is not null after this step).

But there is an optimization for instruction reordering in the JVM’s just-in-time compiler. That is to say, the order of step 2 and step 3 above is not guaranteed, and the final execution order may be 1-2-3 or 1-3-2. If it is the latter, thread two preempts the singleton before 3 executes but before 2, at which point the singleton is non-null (but not initialized), so thread two simply returns the Singleton (step 2 did not execute), uses it, and then naturally reports an error.

We simply declare the Singleton variable as volatile.


public class SingletonDCL {
    private volatile static SingletonDCL singleton;// Declare the variable volatile
    private SingletonDCL(a){}
    public static SingletonDCL getInstance(a){
        if (singleton == null) {
            synchronized (SingletonDCL.class){
                if (singleton == null) {
                    singleton = newSingletonDCL(); }}}returnsingleton; }}Copy the code

The main reason for using volatile is its property of disallowing instruction reordering optimizations. That is, there is a memory barrier (in the generated assembly code) behind assignments to volatile variables, and reads are not reordered to the barrier. For example, the fetch operation must be performed after 1-2-3 or 1-3-2. There is no case where the fetch operation is performed after 1-3.

Another rule for volatile variables, of course, is that writes to a variable occur first (in chronological order) before reads to that variable.

2.4 Static inner Classes

public class SingletonNested {
    // Static inner class
    private static class SingletonHolder{
        private static final SingletonNested singleton = new SingletonNested();
    }
    private SingletonNested(a){}
    public static SingletonNested getInstance(a){
        returnSingletonHolder.singleton; }}Copy the code

Using the JVM’s own mechanism ensures thread-safety issues. Since static singletons are not directly instantiated as member variables of Singleton, SingletonHolder is not instantiated when the class is loaded. The inner class SingletonHolder is loaded when getInstance() is called for the first time. A static singleton is defined in the inner class. The member variable is initialized first, and the Java virtual machine ensures its thread-safety by ensuring that the member variable can only be initialized once. Because the getInstance() method does not have any thread locks, there is no performance impact. s

Because SingletonHolder is private, there is no way to access it other than getInstance(), so it is lazy, reads instances without synchronization, has no performance defects, and does not depend on JDK versions.

2.5 the enumeration

public enum SingletonEnum {
    SINGLETON;
    public void doSomeThing(a) {}}Copy the code

We can access instances through singletonene.singleton, which is much simpler than calling getInstance(). Creating enumerations is thread-safe by default, so you don’t need to worry about double Checked locking, and it also prevents deserialization from causing new objects to be recreated.

summary

Regardless of how the singleton pattern is implemented, the core idea is the same:

  • 1. Privatize the constructor, fetching a unique instance at a time through a static method
  • 2. Thread safety

Usage Scenarios:

  • 1. Objects that need to be created and destroyed frequently.
  • 2. Frequently used objects that take too much time or resources to create.
  • 3. Tool objects.
  • 4. Objects that frequently access databases or files.

In general, it is better to use the hunchman style directly, but it is recommended to use the DCL method and static inner class method to create the singleton pattern. If deserialization is involved try to create objects using enumerations to implement singletons. Of course, enumeration singletons have the advantage of being simple, but enumeration is rarely used in most applications and is not very readable and recommended.

Three, extension,

3.1 Preventing deserialization

Using enumerations above prevents deserialization from causing new objects to be recreated. So how do the other ways of implementing the singleton prevent deserialization from causing the creation of new objects? That’s deserialization. See the serialization article.

Deserialization provides a special hook function with a private readResolve() function that lets developers control deserialization of objects.

public class SingletonDCL implements Serializable {
    private volatile static SingletonDCL singleton;// Declare the variable volatile.private Object readResolve(a) throws ObjectStreamException {
        returnsingleton; }}Copy the code

Return the singleton object in the readResolve method instead of regenerating a new object.

3.2 Volatile keyword

The Java memory model specifies that all variables are stored in main memory. Each thread also has its own working memory, which holds variables used by the thread (these variables are copied from main memory). All operations (reads, assignments) by a thread to a variable must be done in working memory. Different threads cannot directly access variables in each other’s working memory, and the transfer of variable values between threads needs to be completed through the main memory.

role

  • 1. Thread visibility

When a shared variable is volatile, it guarantees that the value is immediately updated to main memory, and that it will read the new value in memory when another thread needs to read it.

  • 2. Command reorder

Before the instruction is added, it is executed concurrently, with the first thread executing halfway through and another thread possibly starting to execute. With the volatile keyword, the different threads are executed sequentially and step by step. For example, 2.3 double-checked locks above.