Summary of the singleton

The Singleton Pattern is one of the simplest design patterns in Java. It involves a single class that is responsible for creating its own objects while ensuring that only a single object is created. To put it simply, there is only one instance of a class in an application, and you can’t go to new because constructors are private and usually get their instances through the getInstance() method.

  • Main solution: a globally used class is frequently created and destroyed.
  • When to use: When you want to control the number of instances and save system resources.
  • How to solve this problem: Check whether the system already has this singleton, return if yes, create if no.
  • Key code: The constructor is private and usedgetInstance()To get an instancegetInstance()The return value of is a reference to an object, not a new instance, so do not misinterpret multiple objects.

The singleton mode must meet the following conditions:

  1. A singleton class can have only one instance;
  2. A singleton class must create its own unique instance;
  3. The singleton class must provide this instance to all other objects.

Usage scenarios for the singleton pattern:

  1. Require production of unique serial numbers.
  2. Counters on the WEB, instead of being added to the database with each refresh, are cached with singletons first.
  3. Creating an object consumes too many resources, such as I/O connections to the database.

The singleton pattern is also easy to implement, as shown in the implementation code below.

Implementation method

1. Lazy writing

Lazy: Threads are not safe

Lazy, as the name implies, loads lazily and instantiates objects only when the singleton is first used. The first time you call it, you initialize it, and if you have a lot of work to do, you get a performance delay, and then it’s hungry-chic.

public class Singleton {
    private static Singleton instance;
    private Singleton(a) {}
    
    public static Singleton getInstance(a) {
        if (instance == null) {
            instance = new Singleton();
        }
        returninstance; }}Copy the code

For the code above, if there are two threads, thread A executes somewhere and reads instance for null, and then the CPU is taken away by thread B before instance A instantiates it. Therefore, thread B still reads instance with null, so it instantiates nstance. The CPU is then taken by thread A. At this point, thread A instantiates instance again because it has read the value of instance and considers it null. Therefore, thread A and thread B do not return the same instance.

Lazy: Lock to make thread safe

public class Singleton {
    private static Singleton instance;
    private Singleton(a) {}public static synchronized Singleton getInstance(a) {
        if (instance == null) {
            instance = new Singleton();
        }
        returninstance; }}Copy the code

Starting with version 1.0, every object in Java has an internal lock, and that lock has an internal condition that governs the threads that attempt to enter synchronized methods, and the condition that governs the threads that invoke wait. If a method is declared with the synchronized keyword, the object’s lock protects the entire method. That is, to call this method, the thread must acquire an internal object lock.

One problem with this solution is that if there are 100 threads executing simultaneously, each time the getInstance() method is executed, the lock must be acquired before the method body is executed. If there is no lock, you have to wait, which takes a long time and feels like serial processing. Disadvantages: Low performance and large synchronization range. After instacne was instantiated, the instance was still synchronized, which was inefficient and the synchronization scope needed to be reduced. So use the following solution:

Lazy: Reduce lock granularity

public class Singleton {
    private static Singleton instance;
    private Singleton(a) {}
    
    public static Singleton getInstance(a) {
        if (instance == null) {
            synchronized(Singleton.class) {
                instance = newSingleton(); }}returninstance; }}Copy the code

Is there no problem in this way? In the same way, thread A reads instance as null and the CPU is seized by thread B. Thread B recognizes instance as null and starts executing the code in the synchronized code block to instantiate instance. Thread A then recaptured the CPU and, since thread A had previously determined that instance was null, began to execute the synchronized code block following it. It also instantiates instance. As a result, two different instances will be created. ** Disadvantages: ** Although the synchronization scope is narrowed to improve performance, it is still possible to execute instance=new Singleton() multiple times, thus leading to a double check lock.

Lazy: double check lock

public class Singleton {
    private static Singleton instance;
    private Singleton(a) {}
    
    public static Singleton getInstance(a) {
        if (instance == null) {
            synchronized(Singleton.class) {
               if (instance == null) {
                   instance = newSingleton(); }}}returninstance; }}Copy the code

However, double-checked locking does not necessarily mean that there are no thread safety issues.

Problems with instruction reorder

The Java Memory Model does not limit processor reordering. Instatnce = new Singleton() is not an atomic statement, but can be divided into the following steps:

  1. Apply for a piece of memory space;
  2. Instantiate objects in this space;
  3. An instance reference refers to the space address (not null if instance refers to the allocated memory).

For the above steps, the instruction reorder is most likely not performed in the order of steps 1, 2, and 3 above. For example, perform step 1 to request a memory space, and then perform step 3 to reference instance to the address of the newly requested memory space. So, when it does step 2, it checks for instance, and since instance already refers to an address, it’s no longer null, and therefore, it doesn’t instantiate the object. This is known as the instruction reorder security problem. So, how to solve this problem?

Resolves instruction reordering

Add the volatile keyword because volatile prevents instruction reordering. Volatile ensures that instance 1, 2, and 3 are executed in the same order. Instance 3 will not be executed until instance 1 and 2 are executed. This ensures that the third step (instance assignment) is the last, so that instance is not null when the object is not initialized. Thus, the correct singleton pattern is implemented. The specific code is as follows:

Lazy: double check lock the ultimate way to write

public class Singleton {
    private volatile static Singleton instance;    
    private Singleton(a) {}
    
    public static Singleton getInstance(a) {
        if (instance == null) {
            synchronized (Singleton.class) {
               if (instance == null) {
                   instance = newSingleton(); }}}returninstance; }}Copy the code

Lazy loading: Lazy loading is also called lazy loading. The core idea of lazy loading is to delay the instantiation of an object until it is actually called. The advantage of lazy loading is that the consumption of resources during the instantiation of a large number of objects is reduced instead of the object being instantiated in advance when the program is initialized. Lazy loading also makes the instantiation code of the object separate from the initialization method, thus improving the readability of the code so that it can be better organized.

2. Hungry Chinese writing

Hanchian instantiates a static object at the same time the class is created. Regardless of whether the singleton is used later, it takes up some memory, but is correspondingly faster on the first call because its resources have already been initialized.

public class Singleton { 
    private static Singleton instance = new Singleton();
    private Singleton(a) {}
    public static Singleton getInstance(a) {
        returninstance; }}Copy the code

This approach is based on the Classloder mechanism. Singleton is created when the class is loaded to ensure the singleton of the instance. Class loading mechanism guarantee singletons are explained below.

Static inner classes

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

This method also uses the classloder mechanism to ensure that the instance is initialized with only one thread. When the Singleton class is loaded, the instance will be instantiated (without lazy loading). This way, the instance will not be initialized if the Singleton class is loaded. Because the SingletonHolder class is not actively used, instance is instantiated by explicitly loading the SingletonHolder class only when the getInstance method is explicitly called. Imagine if instantiating instance is expensive, I should let it lazily load; On the other hand, I don’t want to instantiate the Singleton class when it’s loaded, because I can’t be sure that the Singleton class might be loaded for active use elsewhere, so it’s obviously not appropriate to instantiate it at this point. At this point, it makes sense to use static inner class rather than hungry.

The singleton pattern for hanhanian and static inner class implementations is thread-safe because static is used and then thread-safe when the class is loaded. Synchronized is used indirectly, though not directly.

Thread-safety guarantees during class loading

Both static inner classes and hanhanian implementations define static member variables to ensure that singletons can be instantiated during class initialization. This takes advantage of the thread-safety mechanism of the ClassLoader. The loadClass method of the ClassLoader uses the synchronized keyword when loading classes. So, unless overridden, this method is thread-safe by default throughout the load process, so object creation during class loading is thread-safe as well.

4. The enumeration

Enumeration classes implementing the singleton pattern are pretty hardcore, because enumeration types are thread-safe and only loaded once. The use of enumerated classes to implement the singleton pattern is the only one of all singleton implementations that cannot be broken.

public enum EnumSingleton {
    
    INSTANCE;
    
    public EnumSingleton getInstance(a){
        returnINSTANCE; }}Copy the code

** So how is enumeration singletons thread-safe? ** To see how it works, take a look at the decompiled code:

public final class  EnumSingleton extends Enum<EnumSingleton> {
    public static final  EnumSingleton  ENUMSINGLETON;
    public static  EnumSingleton[] values();
    public static  EnumSingleton valueOf(String s);
    static {};
 }
Copy the code

The enumeration layer is implemented by relying on the Enum class, whose member variables are static and instantiated in a static block of code, similar to Hancock, so it is naturally thread-safe. So enumerations are actually synchronized.

The clone, readObject, and writeObject methods ensure that enumerations cannot be copied by cloning, serializing, or deserializing enumerations. This ensures that the enumeration variable is only an instance, that is, singleton.

This approach, advocated by Effective Java author Josh Bloch, is not only thread-safe and avoids multithreaded synchronization issues, but also prevents deserialization from recreating new objects, a strong barrier.

Problems solved: Avoiding reflection attacks and avoiding serialization issues

conclusion

implementation

  1. To implement singleton pattern, thread safety must be considered, and the way to achieve safety is locking.
  2. Lazy use synchronized directly; Use static to implement thread safety
  3. Use optimistic locking CAS for implementation and ThreadLocal for (non-strictly singleton) thread safety.

Advantages of the singleton pattern

  1. There is only one instance in memory, which reduces the memory overhead, especially the frequent creation and destruction of instances (such as the management school home page cache);
  2. Avoid multiple resources (such as write file operations).

Disadvantages of the singleton pattern:

  1. No interface, no inheritance
  2. In conflict with the single responsibility principle, a class should only care about internal logic and not how it is instantiated externally.

The author information

Hi, I’m CoderGeshu, a programmer who loves life. Please give me a thumbs up if this article is helpful to you: 👍👍👍

In addition, welcome to pay attention to my public account of the same name: CoderGeshu, a public account dedicated to sharing programming technology knowledge!!

One person can go fast, while a group of people can go far…