Teach you how to use the singleton pattern of design pattern

Singleton mode of design mode

definition

A class has only one instance. The class provides a global access point for external access to the instance.

For example, windows can only open one task manager, which can avoid the waste of memory resources caused by opening multiple task manager windows.

characteristic

  1. A singleton class has only one instance object;
  2. The singleton object must be created by the singleton class itself;
  3. The singleton class provides a global access point for accessing the singleton.

Advantages and disadvantages

advantage

  • Singleton mode can ensure that there is only one instance in memory and reduce the memory overhead.
  • Multiple occupation of resources can be avoided.
  • Set global access points in singleton mode to optimize and share resource access.

shortcoming

  • The singleton mode generally has no interface and is difficult to expand. If you want to expand, there is no second way except to modify the original code, which violates the opening and closing principle.
  • In concurrent testing, singleton mode is not conducive to code debugging. During debugging, if the code in the singleton is not executed, a new object cannot be simulated.
  • The function code of singleton mode is usually written in a class. If the function design is unreasonable, it is easy to violate the principle of single responsibility.

Structure and Implementation

Generally, the constructors of ordinary classes are public, and external classes can generate multiple instances through * * new constructor() *.

Before using Spring, it is called through new.

If the constructor of a class is set to private, the external class cannot call the constructor, let alone generate multiple instances.

At this time, the class itself must define a static private instance and provide a static public function to create or obtain the static private instance.

structure

The main roles are: singleton class and access class.

  1. Singleton class: a class that contains an instance and can create the instance itself.
  2. Access class: a class that uses a singleton.

realization

There are two kinds of singleton modes: lazy and hungry.

Lazy means that the instance object will not be created until it is used for the first time.

Hungry Chinese means that the single instance object will be created when the class is loaded.

Lazy single case

1. Lazy singleton (thread unsafe)
public class Singleton {
    //Private construction method
    private Singleton() {
    }
    //Declare variable
    private static Singleton instance;
	//External call method
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
Single thread call
public static void main(String[] args) {
    System.out.println(Singleton.getInstance());
    System.out.println(Singleton.getInstance());
    System.out.println(Singleton.getInstance());
}

When the first call is made, an instance of new will be sent, and the second time, the previous instance of new can be directly obtained.

Output results:

com.ssw.config.thread.Singleton@59a6e353
com.ssw.config.thread.Singleton@59a6e353
com.ssw.config.thread.Singleton@59a6e353

The above code has no problem in the case of single thread. When multiple threads call getInstance() in parallel, multiple instances will be created.

Multithreaded call

For multi-threaded asynchronous calls, the main method must not be used to test. Here I write a new interface and call it through the address.

Create a new TestService class with t1, t2 and t3 methods. Call getInstance() method to output the instance.

The three methods are changed to asynchronous execution.

@Service
public class TestService {
    private static final Logger log = LoggerFactory.getLogger(TestService.class);

    @Async
    public void t1() {
        log.error("t1 example:{}", Singleton.getInstance());
    }

    @Async
    public void t2() {
        log.error("t2 example:{}", Singleton.getInstance());
    }

    @Async
    public void t3() {
        log.error("t3 example:{}", Singleton.getInstance());
    }
}

Control layer, inject TestService class, write a method with address of / a1, and call t1, t2 and t3.

@Autowired
private TestService testService;

@PostMapping("/a1")
public void a1() {
    testService.t1();
    testService.t2();
    testService.t3();
}

Output results:

2021-12-30 10:47:09 ERROR c.s.c.b.c.TestService.t1(20): t1 example: com.ssw.config.thread.Singleton@7a15471c
2021-12-30 10:47:09 ERROR c.s.c.b.c.TestService.t3(30): t3 example: com.ssw.config.thread.Singleton@423eaa58
2021-12-30 10:47:09 ERROR c.s.c.b.c.TestService.t2(25): t2 example: com.ssw.config.thread.Singleton@37cb9672

You can see that each instance is different.

This is a concurrency problem. What I needed was one result, but now it has become three results.

Question: what is the result of executing the / a1 method again?

2. Lazy singleton (thread safe)

To solve the thread insecurity problem above, the simplest way is to set the entire getInstance() method to synchronized

public class Singleton {
    private static Singleton instance;
	//Private construction method
    private Singleton() {
    }
	//External call method
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

Only synchronized is added on getInstance().

Restart the project, call the / a1 method, and view the results:

2021-12-30 10:55:53 ERROR c.s.c.b.c.TestService.t2(25): t2 example: com.ssw.config.thread.Singleton@651dea56
2021-12-30 10:55:53 ERROR c.s.c.b.c.TestService.t1(20): t1 example: com.ssw.config.thread.Singleton@651dea56
2021-12-30 10:55:53 ERROR c.s.c.b.c.TestService.t3(30): t3 example: com.ssw.config.thread.Singleton@651dea56

It can be seen that setting synchronization solves the problem that multithreading will create multiple instances.

3. Lazy single case (double check lock)

synchronized synchronization is added to solve the problem of creating multiple instances in the case of multiple threads, but the efficiency is not high, because when calling getInstance() method, only one thread can call, and other threads must wait.

However, the synchronization operation is only required when it is called for the first time, that is, when the singleton instance object is created for the first time. (that is, the first time I call to create, I don't need to synchronize the subsequent calls, just return the results directly). It's not necessary for each thread to hold a lock to operate.

This leads to a double check lock.

double checked locking pattern is a method of locking using synchronous blocks.

It is called double check lock because instance == null will be checked twice, one outside the synchronization block and one inside the synchronization block.

Why do I have to check again in the synchronization block? Because multiple threads may enter the if outside the synchronization block together, multiple instances will be generated if no secondary verification is performed in the synchronization block.

public class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        //First inspection
        if (instance == null) {
            synchronized (Singleton.class) {
                //Second inspection
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

This code looks perfect, but there will be a problem with instance = new Singleton().

Problem: null pointer problems may occur in multithreaded situations.

This sentence does three things in the jvm:

  1. Allocate memory to instance
  2. Call Singleton's constructor to initialize member variables
  3. Point the instance object to the allocated memory space (after this step, instance will be non null)

There is an optimization of instruction reordering in the JVM's real-time compiler, that is, the order of the second and third steps cannot be guaranteed, and the possible execution order is 1-2-3 or 1-3-2.

If it is the latter, thread 2 preempts 3 after execution and before 2 is executed. At this time, instance is not null (but not initialized), and thread 2 will directly return instance.

We just need to declare the instance variable volatile.

public class Singleton {
    private volatile static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        //First inspection
        if (instance == null) {
            synchronized (Singleton.class) {
                //Second inspection
                if (instance == null) {
                    System.out.println("implement new");
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

The main reason for using volatile is that it has a feature: it prohibits instruction rearrangement optimization and ensures visibility and order.

The difference between volatile and synchronized
  • volatile tells the jvm that the value of the current variable in the register area is uncertain and needs to be read from main memory. synchronized is to lock the current variable. Only the current thread can access the variable, and other threads are blocked.
  • volatile can only be used at the variable level; synchronized can be used at the variable, method, and class levels.
  • volatile can only realize the modification visibility of variables and cannot guarantee atomicity; synchronized ensures the visibility and atomicity of variable modification
  • volatile does not cause thread blocking; synchronized may cause thread blocking.
  • Variables marked volatile are not optimized by the compiler; Variables marked synchronized can be optimized by the compiler
4. Lazy singleton (static internal class recommended *)

The static internal class singleton mode instance is created by the internal class. Because the JVM will not load the static internal class in the process of loading the external class, it will be loaded only when the method / attribute of the internal class is called. The static attribute is modified by static to ensure that it is instantiated only once, and the instantiation order is strictly guaranteed, which solves the problem of instruction reordering.

public class Singleton {
    //Private construction method
    private Singleton() {
    }
    //Define static inner classes
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    //Provide public access
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

Output results:

2021-12-30 11:50:39 ERROR c.s.c.b.c.TestService.t2(25): t2 example: com.ssw.config.thread.Singleton@598414b8
2021-12-30 11:50:39 ERROR c.s.c.b.c.TestService.t3(30): t3 example: com.ssw.config.thread.Singleton@598414b8
2021-12-30 11:50:39 ERROR c.s.c.b.c.TestService.t1(20): t1 example: com.ssw.config.thread.Singleton@598414b8

The static inner class uses the JVM mechanism to ensure thread safety;

SingletonHolder is private. There is no way to access it except getInstance() method, so it is lazy;

Hungry Han style single case

1. Hungry Chinese singleton (static member variable)

The feature is that once the class is loaded, a singleton is created to ensure that the singleton exists before calling the getInstance method.

public class HungrySingleton {
    private static final HungrySingleton instance = new HungrySingleton();

    private HungrySingleton() {
    }

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

At the same time of class creation, a static object has been created for the system to use, which will not be changed in the future. Therefore, it is thread safe and can be directly used for multithreading without problems.

However, this method does not belong to lazy loading, because as long as the program starts, it will be created whether it is used or not.

2. Hungry Chinese singleton (static code block)
public class Singleton {
    //private constructors 
    private Singleton() {
    }
    private static Singleton instance;
    //Static code block
    static {
        instance = new Singleton();
    }
    public static Singleton getInstance() {
        return instance;
    }
}
3. Hungry Chinese single example (recommended for enumeration *)

Creating enumerations is thread safe by default and prevents deserialization from causing the re creation of new objects.

public enum SingletonEnum {
    INSTANCE;
}

Caller:

public static void main(String[] args) {
    SingletonEnum singletonEnum = SingletonEnum.INSTANCE;
    SingletonEnum singletonEnum1 = SingletonEnum.INSTANCE;
    System.out.println(singletonEnum == singletonEnum1);
}

Return result:

true

summary

  1. It is recommended to use hungry man enumeration without considering the waste of memory space.

  2. It is recommended to use the lazy static inner class method when loading when necessary.

Destroy singleton mode

Breaking the singleton mode means that the singleton class defined above can create multiple instances, except enumeration.

There are two ways of destruction: serialization, deserialization and reflection.

Serialization and deserialization mode is corrupted

Example:

For example, we use the static inner class method

public class Singleton implements Serializable {
    //Private construction method
    private Singleton() {
    }

    //Define static inner classes
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    //Provide public access
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

Defines to write the acquired instance to a file

/**
 * Write data (objects) to a file
 * @throws Exception
 */
public static void wirte() throws Exception {
    //1. Get object
    Singleton singleton = Singleton.getInstance();
    //2. Create an output stream object
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\a.txt"));
    //3. Write object
    objectOutputStream.writeObject(singleton);
    //4. Release objects
    objectOutputStream.close();
}

Read instances in file

/**
 * Read data from file (object)
 * @throws Exception
 */
public static void read() throws Exception {
    //1. Create an input stream object
    ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\a.txt"));
    //2. Read object
    Singleton singleton = (Singleton) objectInputStream.readObject();
    System.out.println(singleton);
    //Release resources
    objectInputStream.close();
}

Caller:

First call the write method and then read it twice to see whether the results are consistent.

public static void main(String[] args) throws Exception {
    //1. Write files
    wirte();
    //2. Reading documents
    read();
    read();
}

Output results:

com.ssw.config.thread.Singleton@239963d8
com.ssw.config.thread.Singleton@3abbfa04

Solution to the destruction of serialization and deserialization

The solution of serialization and deserialization breaking the singleton pattern.

Add the readResolve() method to the Singleton class. It is called by reflection during deserialization. If this method is defined, it returns the value of this method. If it is not defined, it returns the new object.

Add readResolve() method to static inner class

//When deserializing, the method will be called automatically and the return value of the method will be returned directly.
public Object readResolve() {
    return SingletonHolder.INSTANCE;
}

Write once and read twice again.

Output results:

com.ssw.config.thread.Singleton@36f6e879
com.ssw.config.thread.Singleton@36f6e879

Problem solving.

Source view
Singleton singleton = (Singleton) objectInputStream.readObject();

View the readObject() method,

readObject0() method, tc=TC_OBJECT

case TC_OBJECT:
    return checkResolve(readOrdinaryObject(unshared));

In the readOrdinaryObject() method

If there is a readResolve method, execute the readResolve method.

Reflection failure

Example:

Or use static inner classes

public class Singleton implements Serializable {
    //Private construction method
    private Singleton() {
    }

    //Define static inner classes
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    //Provide public access
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

Caller:

public static void main(String[] args) throws Exception {
    //Get bytecode object
    Class<Singleton> singletonClass = Singleton.class;
    //Get parameterless construction object
    Constructor<Singleton> constructor = singletonClass.getDeclaredConstructor();
    //Cancel access check
    constructor.setAccessible(true);
    //create object
    Singleton s1 = constructor.newInstance();
    Singleton s2 = constructor.newInstance();
    System.out.println(s1 == s2);
}

Two objects are created and the result of the call is:

false

Solutions to reflection corruption

Reflection is to create an object by obtaining a parameterless construction object.

Therefore, the static inner class needs to modify the construction method

Increase the identifier and set it to true when it is created for the first time, otherwise a runtime exception will be thrown.

private static boolean flag = false;

//Private construction method
private Singleton() {
    synchronized (Singleton.class) {
        //If it is true, it indicates that it has been created. Throw a runtime exception.
        if (flag) {
            throw new RuntimeException("Cannot create multiple objects");
        }
        //If false, set flag=true for the first creation
        flag = true;
    }
}

Static inner class complete code

public class Singleton implements Serializable {

    private static boolean flag = false;

    //Private construction method
    private Singleton() {
        synchronized (Singleton.class) {
            //If it is true, it indicates that it has been created. Throw a runtime exception.
            if (flag) {
                throw new RuntimeException("Cannot create multiple objects");
            }
            //If false, set flag=true for the first creation
            flag = true;
        }
    }

    //Define static inner classes
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    //Provide public access
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

Call result:

Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at com.ssw.core.blog.controller.Test2.main(Test2.java:22)
Caused by: java.lang.RuntimeException: Cannot create multiple objects
	at com.ssw.config.thread.Singleton.<init>(Singleton.java:17)
	... 5 more

example

1. Singleton mode in JDK source code

Such as Java Lang. runtime uses starving singleton (static member variable)

Methods for external calls

public static Runtime getRuntime() {
        return currentRuntime;
    }

2. Singleton pattern in spring source code

In Spring, dependency injection bean instances are singleton by default. Bean has two modes, singleton and prototype.

  • Singleton (singleton): only one shared instance exists, and all requests for this bean will return a unique instance.
  • prototype (multiple instances): each request for this bean will create a new bean instance, similar to new.

For example, @ Autowired, @ Resource, @ Service, @ Mapper, @ Component all belong to singleton mode by default.

Take @ Autowired as an example

Example

Create a new users interface

public interface IUsers {
    /**
     * pick up information
     * @return
     */
    boolean getInfo();
}

Create a new implementation class for the users interface

@Service
public class UsersService implements IUsers {
    @Override
    public boolean getInfo() {
        return true;
    }
}

Control layer call (two control layers are written for comparison)

@RestController
@RequestMapping("users")
public class Users1Controller {
    private static final Logger log = LoggerFactory.getLogger(Users1Controller.class);

    @Autowired
    private IUsers users;

    @RequestMapping("a1")
    public void a1() {
        log.info("a1: users={},Return result:{}", users, users.getInfo());
        IUsers iUsers = new UsersService();
        log.info("a1 new Come out: users={},Return result:{}", iUsers, iUsers.getInfo());
    }
}
@RestController
@RequestMapping("users1")
public class Users2Controller {
    private static final Logger log = LoggerFactory.getLogger(Users2Controller.class);

    @Autowired
    private IUsers users;

    @RequestMapping("a2")
    public void a2() {
        log.info("a2: users={},Return result:{}", users, users.getInfo());
        IUsers iUsers = new UsersService();
        log.info("a2 new Come out: users={},Return result:{}", iUsers, iUsers.getInfo());
    }
}

Note: the two control layers inject IUsers interfaces respectively, create a new service layer, and compare the log output.

result

: a1: users=com.demo.service.UsersService@3ae1a7f3,Return result: true
: a1 new Come out: users=com.demo.service.UsersService@235bfd56,Return result: true
: a2: users=com.demo.service.UsersService@3ae1a7f3,Return result: true
: a2 new Come out: users=com.demo.service.UsersService@66ee65ad,Return result: true
conclusion
  1. It can be seen that the instance injected multiple times is the same instance.
  2. Each new instance is created, so each instance is different, which will cause a waste of resources.

Is Spring's bean injection a lazy singleton or a hungry singleton?

extend

As mentioned above, in Spring, dependency injection bean instances are singleton by default. Bean has two modes, singleton and prototype.

  • Singleton (singleton): only one shared instance exists, and all requests for this bean will return a unique instance.
  • prototype (multiple instances): each request for this bean will create a new bean instance, similar to new.

Multi instance implementation

The above examples all use the UsersService bean. We only need to add the @ Scope annotation to change the bean into the multi instance mode.

@Scope("prototype")
@Service
@Scope("prototype")
public class UsersService implements IUsers {
    @Override
    public boolean getInfo() {
        return true;
    }
}

Other classes do not need to be modified

test result

a1: users=com.demo.service.UsersService@5c8847c1,Return result: true
a1 new Come out: users=com.demo.service.UsersService@405b8b1e,Return result: true
a2: users=com.demo.service.UsersService@6a7b3cfc,Return result: true
a2 new Come out: users=com.demo.service.UsersService@b529dd6,Return result: true

You can see that each instance is different. Each request for this bean creates a new instance.

@What is Scope

  1. Singleton singleton mode, that is, there is only one instance in the container. No matter how to obtain a bean, there is only one instance. The definition of a singleton type bean starts from the container startup to the first requested instantiation. As long as the container does not destroy or exit, the single instance of this type of bean will survive. Typical singleton mode.
  2. In the prototype multi instance mode, the spring container will regenerate a new Bean instance to the requester every time. The instantiation and property setting of the object of this type are the responsibility of the container. However, after the preparation is completed and the object instance is given to the requester, the container will no longer have a reference to the object, The party who gets the object instance needs to be responsible for the subsequent life cycle of the object instance (for example, destruction).
  3. Request creates an instance for the same request, and the bean is valid in the current request. This can be understood as a special multi instance mode. When the request ends, the life cycle of the object ends, and they do not interfere with each other.
  4. A session represents an instance of the same session, and a session id corresponds to a session.
  5. Global session is only useful in web programs based on porlet. It maps to the global session of porlet. This mode is used in ordinary servlet s, and the container will treat it as the scope of ordinary session.

The most commonly used are single singleton and multiple prototype.

Keywords: Java Design Pattern Singleton pattern

Added by gersh on Tue, 04 Jan 2022 22:30:30 +0200