introduce

The singleton pattern is one of the most widely used, and probably the only design pattern used by many beginning or junior engineers. When applying this pattern, the class of the singleton object must ensure that only one instance exists. Many times the entire system only needs to have one instance class. It helps our invocation to avoid creating multiple instances of the same class, such as a network request, image request/download, database operation, etc. If the same object is created frequently, it is a resource drain, so there is no reason for them to construct multiple instances. Singletons can be used when this function is needed globally to avoid repeated creation. This is the singleton usage scenario.

define

Ensure that there is only one instance of a class and that you instantiate it yourself and make it available to the entire system.

Usage scenarios

The singleton design pattern can be considered when a class is reused in an application to avoid resource consumption resulting from multiple creations.

Singleton UML class diagrams

There are several key points to realize the singleton pattern:

  1. Constructors are not public and are generally private;
  2. Return a singleton via a static method or enumeration;
  3. Ensure that there is one and only one object for a singleton class, especially in multithreaded environments;
  4. Ensure that singleton class objects are not rebuilt objects when deserialized.

Singleton sample

The hungry type

The singleton pattern is one of the simpler design patterns, with only one singleton class and no other hierarchies and abstractions. This pattern ensures that the class can only generate one object, usually when the class consumes a lot of resources or does not have multiple instances. For example:

public class DaoManager {

    /** ** */
    private static DaoManager instance = new DaoManager();

    private DaoManager(a){}

    public static DaoManager getInstance(a){
        returninstance; }}Copy the code

test

    @Test
    public void test(a){
        String dao = DaoManager.getInstance().toString();
        String dao1 = DaoManager.getInstance().toString();
        String dao2 = DaoManager.getInstance().toString();
        String dao3 = DaoManager.getInstance().toString();


        System.out.println(dao);
        System.out.println(dao1);
        System.out.println(dao2);
        System.out.println(dao3);
    }
Copy the code

Output

com.devyk.android_dp_code.singleton.DaoManager@28ba21f3
com.devyk.android_dp_code.singleton.DaoManager@28ba21f3
com.devyk.android_dp_code.singleton.DaoManager@28ba21f3
com.devyk.android_dp_code.singleton.DaoManager@28ba21f3
Copy the code

The DaoManager object is static, so it is initialized at the time it is declared. This ensures that the object is unique. DaoManager outputs the same address all four times. The core of this implementation is to privatize the constructor of the DaoManager class, so that external programs cannot construct new objects, but can only return an object via getInstance().

Lazy mode

Lazy mode declares a static object and initializes it on the first call, whereas hungry paper is initialized at declaration time. The lazy implementation is as follows:

public class DaoManager2 {
    
    private static DaoManager2 instance;
    
    private DaoManager2(a){}

    /** * thread safe lazy *@return* /
    public static synchronized DaoManager2 getInstance(a){
        if (null == instance) {
            instance = new DaoManager2();
        }
        returninstance; }}Copy the code

Careful readers may have noticed that the synchronized keyword is added to the getInstance() method, which is a synchronization method that guarantees singleton object uniqueness in multithreaded situations. If you think about it, one of the biggest problems with the lazy singleton pattern is that even though instance has been initialized, every call is checked synchronously, which consumes unnecessary resources.

Finally, the advantage of slackish singleton pattern is that the singleton can only be initialized when it is reused, which saves resources to a certain extent. The disadvantage is that the first load needs to be initialized, which is a little slow. The biggest problem is that getInstance is synchronized every time it is called, causing unnecessary overhead. This mode is generally not recommended.

Double Check Lock implements singletons

DCL can not only initialize the singleton when needed, but also ensure thread safety. Besides, instance is called without synchronization lock after the singleton object is initialized. The code is as follows:

public class DaoManager3 {

    private static DaoManager3 sinstance;

    private DaoManager3(a) {}/** * thread safe lazy **@return* /
    public static DaoManager3 getInstance(a) {
        if (null == sinstance) {
            synchronized (DaoManager3.class) {
                if (null == instance)
                    sinstance = newDaoManager3(); }}returnsinstance; }}Copy the code

The highlight of this code is the getInstance method. You can see that the getInstance method nulifies instance twice. The first level of judgment is mainly to avoid unnecessary synchronization, and the second level is to create instances in the case of NULL. If it seems a little confusing, here’s how:

sinstance = new DaoManager3();
Copy the code

This step is actually performed in the JVM in three steps:

  1. Create memory space in heap memory;
  2. Instantiate the parameters in DaoManager3 in heap memory.
  3. Point objects to heap memory space;

Since the Java compiler allowed processors to execute out of order prior to JDK 1.5, and the JMM could not guarantee that caches, registers (the Java memory model) would execute in 1, 2, and 3 order. Therefore, it is possible to execute 3 before 2 is executed. If the sinstance is switched to thread B, the sinstance is not empty and will be used directly. In this case, an exception will occur. And not easy to reproduce and not easy to track is a hidden BUG.

Volatile: Private volatile static DaoManager3 sinstance; DCL failure can be solved. Volatile ensures that sInstance is read in main memory each time, sacrificing a bit of efficiency, but it doesn’t hurt.

Advantages of DCL: High resource utilization. The singleton is instantiated only when getInstance is executed for the first time.

Disadvantages: DCL is somewhat slow to load the first time, and occasionally fails due to the Java memory model. There are also certain defects in high concurrency environments, although the occurrence rate is very small.

The DCL pattern is the most used pattern, it can be instantiated as needed, and it can keep singletons unique in most scenarios. Unless your code is used in concurrent scenarios that are more complex or older than JDK 6, this approach will generally suffice.

Static inner class singleton pattern

DCL though to some extent, solves the resource consumption, redundant synchronization, thread safe, but it is still failure problems in some cases, the problem is called double-checked locking failure, at the end of the book Java concurrent programming practice talked about this problem, and points out that the “optimization” is ugly, not in favor of use. The following code is recommended instead.

public class DaoManager4 {
    
    private DaoManager4(a){}

    public static DaoManager4 getInstance(a){
        return DaoManager4Holder.sInstance;
    }

    /** * static inner class ** /
    private static class DaoManager4Holder{
        private static final DaoManager4 sInstance = newDaoManager4(); }}Copy the code

So how can static inner classes be thread-safe? First, let’s look at the loading timing of the class.

Class loading timing: There are only five scenarios in which the JAVA virtual machine initializes classes.

  1. When the bytecode instructions new, getstatic, setstatic, or Invokestatic are encountered, the corresponding Java code scenario is: New when a keyword or an instantiated object, when a static field is read or set (except for final modifiers, where the result has been put into the constant pool at compile time), when a static method of a class is called.
  2. When a java.lang.Reflect method is used to make a reflection call to a class, if the class is not already initialized, it needs to call its initialization method first.
  3. When initializing a class, the initialization of its parent class is triggered first if the parent class has not been initialized.
  4. When the virtual machine starts, the user needs to specify a main class (the class containing the main() method) to execute, and the virtual machine initializes this class first.
  5. When using JDK 1.7 and other dynamic language support, if a Java lang. Invoke. The final analytical results REF_getStatic MethodHandle instance, REF_putStatic, REF_invokeStatic method handles, If the class to which the method handle corresponds has not been initialized, it needs to be initialized first. These five cases are called active references to a class. Note that the qualifier used in the VIRTUAL Machine Specification is “have and only”, so all other reference classes do not initialize the class and are called passive references. Static inner classes are among the passive references.

Again we look back the getInstance () method, which is called DaoManager4Holder. SInstance, take the DaoManager4Holder sInstance object, in the DCL with the above method, The getInstance() method does not call the new object many times, so no matter how many threads call the getInstance() method, it takes the same sInstance object instead of recreating it. When the getInstance() method is called, the DaoManager4Holder replaces the symbolic reference with a direct reference in the DaoManager4 runtime constant pool, and the static object sInstance is actually created. It is then returned by getInstance(), which is the same as hangry mode. How can sInstance be thread-safe during creation? In Understanding the JAVA Virtual Machine, there is a quote:

The virtual machine ensures that a class’s () method is locked and synchronized correctly in a multithreaded environment. If multiple threads initialize a class at the same time, only one thread will execute the class’s () method, and all the other threads will block and wait until the active thread finishes executing the () method. If there is a long operation in the () method of a class, multiple processes can block. (Note that other threads can block, but if they wake up after executing the () method, they will not re-enter the () method. A type is initialized only once under the same loader. In practice, this blocking is often hidden.

Therefore, it can be seen that sInstance is thread-safe in the creation process, so the singleton in the form of static inner class can ensure thread-safe and uniqueness of the singleton, but also delay the instantiation of the singleton.

So, is a static inner class singleton the perfect singleton pattern? In fact, static inner class also has a fatal disadvantage, is the problem of parameter passing, because it is in the form of static inner class to create a singleton, so external parameters can not be passed in, such as Context parameters, so we can create a singleton, static inner class and DCL mode of their own choice.

Enumerated the singleton

Previously explained several singleton pattern implementation methods, these several implementation methods are not slightly troublesome or in some cases will have problems, so there is no simpler implementation method le? Let’s look at the following implementation first.

public enum  DaoManager5 {
    
    INSTANCE;
    
    public void doSomething(a){
        Log.i("DAO->"."Enumeration singleton"); }}Copy the code

That’s right, enumeration singletons!

The simplicity of writing is the biggest advantage of enumeration singletons, enumeration in Java and ordinary classes, not only can have fields, but also can have their own methods. The most important thing is that the default enumeration instance is created thread-safe, and it is a singleton in any case.

Advantages: Enumerations themselves are thread-safe and prevent instance creation through reflection and deserialization.

Disadvantages: Limited to JDK version, not lazy loading.

Use containers to implement the singleton pattern

After learning the above five singleton patterns, finally introduce a container singleton pattern, see the following code implementation:

public class DaoManager6 {

    /** * define a container */
    private static Map<String,Object> singletonMap = new HashMap<>();
    
    private DaoManager6(a){}
    
    public static void initDao(String key,Object instance){
        if (!singletonMap.containsKey(key)){
            singletonMap.put(key,instance);
        }
    }
    
    public static Object getDao(String key){
        returnsingletonMap.get(key); }}Copy the code

At the beginning of the program, the singleton type can be injected into the unified management class, and the corresponding singleton object can be obtained according to the key when using, and the operation can be obtained through the unified interface when using, which reduces the user’s use cost, also hides the specific implementation from the user, and reduces the coupling degree.

Android source singleton pattern

GetSystemService (String name); context.getSystemService(String name); Container singleton mode, using Context.LAYOUT_INFLATER_SERVICE as an example.

Analyze LayoutInflater from the setContentView entry

conclusion

The singleton pattern is one of the most frequently used design patterns in applications, but since the client usually does not have high concurrency, the choice of implementation does not matter much. Of course, the DCL or static inner class singleton pattern is recommended for efficiency and concurrency scenarios.

Note: If the singleton must hold parameters, it is recommended to use weak references to receive parameters. If the type is context-level, it is recommended to use context.getApplication() otherwise it may cause memory leaks.

The code that appears in the article

Thank you for reading, thank you!