Java design pattern optimization - singleton pattern

Keywords: Java jvm JDK Attribute

Overview of Singleton Patterns

Singleton pattern is an object creation pattern used to generate specific instances of a class. Using the singleton pattern ensures that only one instance of the singleton class is generated throughout the system. There are two main benefits:

  1. For frequently created objects, the creation time after the first instantiation is saved.
  2. Because of the reduction of new operation, the frequency of system memory usage will be reduced. Reduce GC pressure and shorten GC pause time

How to create it:

  1. private properties of singletons as classes
  2. A singleton class has a private constructor
  3. Providing a public method for obtaining instances

The role of the singleton pattern:

role Effect
Singleton class Provides a factory for a singleton, returning a singleton instance of a class
Users Get and use singleton classes

Class basic structure:

Implementation of singleton pattern

1. Hungry Chinese Style

public class HungerSingleton {
    //1. Hungry Chinese Style
    //Private constructor
    private HungerSingleton() {
        System.out.println("create HungerSingleton");
    }
    //Private singleton properties
    private static HungerSingleton instance = new HungerSingleton();
    //Method of obtaining singletons
    public static HungerSingleton getInstance() {
        return instance;
    }
}

Be careful:

  1. When static JVM loads a Singleton class, the Singleton is initialized directly. Unable to delay loading. If the Singleton has not been used, the Singleton will waste memory because the static method is initialized.
  2. getInstance() is decorated with static, and Singleton.getInstance() can be used directly to obtain singletons without instantiation.
  3. Because the singleton is created when the class is loaded by JVM, there is no thread security problem.

2. Simple Slacker Style

public class Singleton {
    //2.1 Simple Lazy (Thread Insecurity)
    //Private constructor
    private Singleton() {
        System.out.println("create Singleton");
    }
    //Private singleton property [initialized to null]
    private static Singleton instance = null;
    //Method of obtaining singletons
    public static Singleton getInstance() {
        if(instance == null) {
            //Here instance is instantiated
            //The first time a singleton is invoked, it enters the reaching delay load
            instance = new Singleton();
        }
        return instance;
    }
}
  • Since the synchronized keyword is not used, when thread 1 calls the singleton factory method Singleton.getInstance() and instance is not initialized, thread 2 calls this method to judge instance as null, and also to re-instantiate instance assignment. At this time, multiple instances are generated!
  • For thread safety, you can add synchronized keywords directly to the getInstance method, as follows:
public class Singleton {
    //2.2 Simple Lazy (Thread Safety)
    //Private constructor
    private Singleton() {
        System.out.println("create Singleton");
    }
    //Private singleton property [initialized to null]
    private static Singleton instance = null;
    //The way to get a singleton is to synchronize this method with the synchronized keyword
    public static synchronized Singleton getInstance() {
        if(instance == null) {
            //Here instance is instantiated
            //The first time a singleton is invoked, it enters the reaching delay load
            instance = new Singleton();
        }
        return instance;
    }
}

The problems faced are:

  • Because of the lock on the whole method of getInstance(), the performance is poor in multi-threaded environment.

3.DCL Lazy Man (Double Detection)

In simple lazy type (thread security), locking getInstance() method results in poor performance in multi-threading. Can the scope of the lock be reduced so that locks are not contested every time geInstance() method is called?

DCL(Double Check Locking) dual detection is such an implementation.

Traditional DCL:

public class DCLLazySingleton {
    //3.DCL
    //Private constructor
    private DCLLazySingleton() {
        System.out.println("create DCLLazySingleton");
    }
    //Step 1 private singleton property [initialized as null] volatile guarantees memory visibility and prevents instruction rearrangement
    private static volatile DCLLazySingleton instance = null;
    //Method of obtaining singletons
    public static DCLLazySingleton getInstance() {
        //null is judged here to improve code performance without entering locked blocks when instance has value.
        if(instance == null) {
            //Reduce lock scope because static method method method calls can only use classes without relying on instantiated object locking
            synchronized (DCLLazySingleton.class) {
                //null is judged here to solve multithreading security problems with volatile
                if(instance == null) {
                    instance = new DCLLazySingleton();
                }
            }
        }
        return instance;
    }
}

Be careful:

  1. Problems faced by traditional DCL (not using volatile or prior to JDK 1.8):

    • Since the initialization of the singleton object new DCLLazySingleton() operation is not an atomic operation, because this is a lot of instructions, the jvm may be executed in disorder.

Initialization of the object in thread 1 may not be complete, but the instance object is no longer null at this time. (Memory has been allocated, but the constructor has not yet been executed [there may be some attribute assignments not executed]
Thread 2 retrieves instance instead of null. Thread 2 then gets an instance object whose method has not been executed. Thread security is not guaranteed.

  1. Solutions:

    • With volatile keyword, volatile guarantees memory visibility, memory barrier, and prevents instruction row!
    • With volatile keyword, instance objects that are not executed by the constructor method acquired by thread 2 will be synchronized to thread 2 (volatile memory space) after thread 1 is modified. So it solves the thread security problem.
  2. Reference resources:

4. Slacker Style (Static Internal Class)

public class StaticSingleton {
    //Private constructor
    private StaticSingleton() {
        System.out.println("create StaticSingleton!");
    }
    //Method of obtaining singletons
    public static StaticSingleton getInstance() {
        return SingletonHolder.instance;
    }
    //Static inner classes hold singletons as static attributes.
    //Because static class initialization instance is loaded only when attributes are accessed. So lazy loading is realized. And because JVM guarantees that class loading is thread-safe, it is thread-safe.
    private static class SingletonHolder {
        //Private singleton properties
        private static StaticSingleton instance = new StaticSingleton();
    }
}

Be careful:

  1. Because the StaticSingleton class is loaded, the internal private static class Singleton Holder is not loaded, so the singleton instance is not initialized. When getInstance() is called, Singleton Holder. instance loads Singleton Holder. Because JVM guarantees that the class is loaded thread-safe, so thread-safe.
  2. This method not only can delay loading, but also can not affect performance because of synchronization keywords. It is a relatively perfect realization. Recommended use

5. Enumeration of singletons

public enum EnumSingleton {
    INSTANCE();
    EnumSingleton() {
        System.out.println("create EnumSingleton");
    }
}
  • Thread-safe and resistant to reflection and serialization.
  • Recommended use

Exceptions

The above-mentioned single implementation still faces some special circumstances which can not guarantee the only example:

  1. Reflection calls private constructors.
  2. After serialization, deserialization generates multiple objects. Private readResolve methods can be implemented. readObject() is like a fictitious, directly replacing the original return value with readResolve. As follows:
    private Object readResolve () {
    //Returns the current object
    return instance;
    }

Because the above two situations are quite special, there is no special attention.

Reference Books

"Java Program Performance Optimization" - Ge Yiming et al.

Posted by cedricm on Wed, 07 Aug 2019 06:06:39 -0700