Design mode - singleton mode

1, Definition of singleton mode

Definition: ensure that there is only one instance of a class and provide the global access point of the instance.

The advantage of this is: for some instances, only one global instance is enough. Using the singleton mode can avoid the frequent creation and destruction of a global class, which consumes system resources.

2, Design elements of singleton mode

  • A private constructor (ensure that only singleton classes can create instances themselves)
  • A private static variable (make sure there is only one instance)
  • A public static function (providing calling methods for users)

In short, the construction method of singleton class cannot be modified and used by others; Moreover, the singleton class only creates one instance, which can not be modified and used directly by others; Then, the singleton class provides a calling method. If you want to use this instance, you can only call. This ensures that the global instance is created only once.

3, Six implementations of singleton mode and their advantages and disadvantages

(1) Lazy (thread unsafe)

realization:

public` `class` `Singleton {``   ``private` `static` `Singleton uniqueInstance;` `   ``private` `Singleton() {` `  ``}` `  ``public` `static` `Singleton getUniqueInstance() {``    ``if` `(uniqueInstance == ``null``) {``      ``uniqueInstance = ``new` `Singleton();``    ``}``    ``return` `uniqueInstance;``  ``}``}

Note: do not create an instance first, and then create an instance when it is called for the first time, so it is called lazy.

Advantages: the instantiation is delayed. If this class is not needed, it will not be instantiated, saving system resources.

Disadvantages: threads are unsafe. In a multithreaded environment, if multiple threads enter if (uniqueInstance == null) at the same time, if they have not been instantiated at this time, that is, uniqueInstance == null, then multiple threads will execute uniqueInstance = new Singleton(), Multiple instances will be instantiated;

(2) Hungry man (thread safe)

realization:

public` `class` `Singleton {` `  ``private` `static` `Singleton uniqueInstance = ``new` `Singleton();` `  ``private` `Singleton() {``  ``}` `  ``public` `static` `Singleton getUniqueInstance() {``    ``return` `uniqueInstance;``  ``}` `}

Note: first, whether you need to use this instance or not, you can directly instantiate a good instance (like a starving ghost, so it is called hungry Han style), and then directly adjust the method when you need to use it.

Advantages: instantiate an instance in advance to avoid thread insecurity.

Disadvantages: instantiate the instance directly without delaying instantiation; If the system does not use this instance, or the system needs to use this instance after running for a long time, it will waste the resources of the operating system.

(3) Lazy (thread safe)

realization:

public` `class` `Singleton {``  ``private` `static` `Singleton uniqueInstance;` `  ``private` `static` `singleton() {``  ``}` `  ``private` `static` `synchronized` `Singleton getUinqueInstance() {``    ``if` `(uniqueInstance == ``null``) {``      ``uniqueInstance = ``new` `Singleton();``    ``}``    ``return` `uniqueInstance;``  ``}` `}

Note: the implementation is almost the same as the thread unsafe lazy type. The only difference is that a lock is added to the get method. In this way, when multiple threads access, only the thread that gets the lock can enter the method each time, avoiding the unsafe problem of multiple threads.

Advantages: delaying instantiation saves resources and is thread safe.

Disadvantages: Although the thread safety problem is solved, the performance is reduced. Because even if the instance has been instantiated, there will be no thread safety problems in the future, but the lock is still there. Each time, only the thread that gets the lock can enter the party * * * blocking the thread and waiting too long.

(4) Double check lock implementation (thread safety)

realization:

public` `class` `Singleton {` `  ``private` `volatile` `static` `Singleton uniqueInstance;` `  ``private` `Singleton() {``  ``}` `  ``public` `static` `Singleton getUniqueInstance() {``    ``if` `(uniqueInstance == ``null``) {``      ``synchronized` `(Singleton.``class``) {``        ``if` `(uniqueInstance == ``null``) {``          ``uniqueInstance = ``new` `Singleton();``        ``}``      ``}``    ``}``    ``return` `uniqueInstance;``  ``} ``}

Note: the number of double checks is equivalent to the lazy type that improves thread safety. The disadvantage of thread safe lazy style is that the performance is reduced. The reason is that even if the instance has been instantiated, there will still be locks every time. Now, we have changed the position of the lock and added an additional check. That is, first judge whether the instance already exists. If it already exists, the locked method in the judgment method will not be executed. If multiple threads go in before instantiation, it's OK, because the method inside has a lock, which will only let one thread enter the innermost method and instantiate the instance. In this way, at most, that is, when instantiating for the first time, there will be thread blocking, and there will be no thread blocking in the future.

Why use volatile keyword to modify uniqueInstance instance instance variable?

uniqueInstance = new Singleton(); This code is executed in three steps:

  1. Allocate memory space for uniqueInstance
  2. Initialize uniqueInstance
  3. Point uniqueInstance to the allocated memory address

Of course, the normal execution order is 1 > 2 > 3, but because the JVM has the characteristic of instruction rearrangement, the execution order may become 1 > 3 > 2.
In a single threaded environment, there is no problem with instruction rearrangement; In a multithreaded environment, some threads may get uninitialized instances.
For example, thread A only executes 1 and 3. At this time, thread B calls getUniqueInstance(). If it finds that the uniqueInstance is not empty, it obtains the uniqueInstance instance. However, the uniqueInstance at this time has not been initialized.

The solution is to add a volatile keyword to modify uniqueInstance. Volatile will prohibit the instruction rearrangement of JVM, which can ensure the safe operation in multi-threaded environment.

Advantages: delaying instantiation saves resources; Thread safety; And compared with the thread safe lazy style, the performance is improved.

Disadvantages: volatile keyword also has some impact on performance.

(5) Static internal class implementation (thread safe)

realization:

public` `class` `Singleton {` `  ``private` `Singleton() {``  ``}` `  ``private` `static` `class` `SingletonHolder {``    ``private` `static` `final` `Singleton INSTANCE = ``new` `Singleton();``  ``}` `  ``public` `static` `Singleton getUniqueInstance() {``    ``return` `SingletonHolder.INSTANCE;``  ``}` `}

Note: first, when the external class Singleton is loaded, the static internal class SingletonHolder is not loaded into memory. When the getUniqueInstance() method is called, return singlethonholder. Exe will be run INSTANCE; , Triggered SingletonHolder At this time, the static internal class SingletonHolder will be loaded into memory and the instance of instance will be initialized, and the JVM will ensure that instance is instantiated only once.

Advantages: delaying instantiation saves resources; And thread safety; Performance has also improved.

(6) Enumeration class implementation (thread safe)

realization:

public` `enum` `Singleton {` `  ``INSTANCE;` `  ``//Add the required operation ` ` ` ` ` ` public` `void` `doSomeThing() {` ` ` ` ` ` `} `}

Note: the creation of the default enumeration instance is thread safe, and it is a single instance in any case.

Advantages: simple writing, thread safety, natural prevention of reflection and deserialization calls.

  • Prevent deserialization
    **Serialization: * * the process of converting java objects into byte sequences;
    Deserialization: the process of creating new java objects in memory through these byte sequences;
    Note: deserialization writes a singleton instance object to disk and reads it back, so as to obtain a new instance.
    We need to prevent deserialization and avoid getting multiple instances.
    Enumeration classes naturally prevent deserialization.
    Other singleton modes can override the readResolve() method to prevent deserialization and make the instance uniquely override readResolve():
private` `Object readResolve() ``throws` `ObjectStreamException{``    ``return` `singleton;``}

4, Application scenario of singleton mode

Examples of application scenarios:

  • Site counters.
  • Application log.
  • Reading of configuration objects in Web projects.
  • Database connection pool.
  • Multithreaded pool.
  • ...

Usage scenario summary:

  • For objects that are instantiated frequently and then destroyed, using singleton mode can improve performance.
  • Objects that are often used, but are time-consuming or resource intensive when instantiating. For example, database connection pool. Using singleton mode can improve performance and reduce resource damage.
  • When using control resources such as thread pool, the singleton mode can facilitate the communication between resources.

Keywords: Design Pattern

Added by SusanMoore on Sun, 20 Feb 2022 18:59:06 +0200