Creation of 7 singleton modes

preface

Singleton design pattern is one of the most commonly used design patterns among the 23 design patterns. There are almost singleton design patterns in both tripartite class libraries and daily development The shadow of counting mode. Singleton design pattern provides a solution to ensure the uniqueness of instances in the case of multithreading. Although the singleton design pattern is simple, there are many implementation schemes. Generally, there are seven most common ways.

Hungry man model

The so-called hungry Han style is to create this object first whether you use it or not, so that it can be guaranteed to be a singleton when you use it.

Example:

public class test1 {

	/**
	 * Initialize object directly
	 * */
	private static final test1 INSTANCE = new test1();

	/**
	 * new objects are not allowed from outside
	 **/
	private test1() {

	}

	/**
	 * Get object by unique method
	 * @return
	 */
	public static test1 getInstance() {
		return INSTANCE;
	}
}

Summary: This scheme is the simplest to implement. When test1 is loaded, instance will be created immediately. Therefore, this method can ensure 100% single instance, and instance cannot be instantiated twice. However, instance may not be used for a long time after it is loaded, which means that instance takes more memory.

be careful: If there are few member attributes in a class and it does not occupy much memory resources, you can use hungry Chinese. This is not appropriate if there are heavy resources in a class

Lazy mode

The so-called lazy style is to create it when in use, which can be understood as lazy loading.

Example:

public class test2 {
	private static test2 instance;

	private test2() {
		System.out.println("Class is instantiated");
	}

	public static test2 getInstance() {
		if (instance == null) {
			instance = new test2();
		}
		return instance;
	}

}

Summary: When instance is null, getInstance will first create a new instance, and then return the instance.

be careful: However, this implementation will have thread safety problems. Different object instances will appear when multiple threads obtain them at the same time, which destroys the principle of singleton.

Lazy mode + synchronization method

In order to solve the lazy thread safety problem, we can add the synchronization method

Example:

public class test2 {
	private static test2 instance;

	private test2() {
		System.out.println("Class is instantiated");
	}

	public static synchronized test2 getInstance() {
		if (instance == null) {
			instance = new test2();
		}
		return instance;
	}

}

Summary: This approach ensures lazy loading and 100% instance singleton, but the inherent exclusivity of the synchronized keyword leads to low performance of this method.

Double check lock

Double check is a smart method. In fact, we only need to ensure the synchronization of threads when instance is null, so that only one thread can create objects, while other threads are still used directly. When instance already has an instance, we don't need thread synchronization operation, and we can read directly in parallel. Here we add two properties to the class

Example:

public class test2 {
	private static test2 instance;
	private Object o1;
	private Object o2;

	private test2() {
		o1=new Object();
		o2=new Object();
		System.out.println("Class is instantiated");
      	}
 	public static test2 getInstance() {
 		// When null, enter the synchronization code block, while avoiding the need to enter the synchronization code block every time
 		if (instance == null) {
 			// Only one thread can acquire the lock
 			synchronized (test2.class) {
 				// If Null, create
 				if (instance == null) {
 					instance = new test2();
                		}
            		}
        	}
 		return instance;
    	}

}

Summary: When two threads find instance == null, only one thread is qualified to enter the synchronization code block to complete the initialization of instance. After the subsequent thread enters the synchronization code block again, it will not be created again because instance == null is not tenable. This is a parallel scenario without loading. After the instance is loaded, another thread enters the getInstance method, Just go straight back instance, it will not enter the synchronization code block, so as to improve performance.

be careful: This approach seems perfect and ingenious, which not only meets the lazy loading, but also ensures the uniqueness of instance, but in fact, null pointer exceptions will occur in this way.

Problem of parsing null pointer exception:

In the test2 construction method, we will initialize o1 and o2 resources and Single itself. In fact, these three resources have no contextual constraints. It is very likely that the JVM will reorder them, resulting in instantiation of test2 first and then o1 and o2. In this way, when using test2, null pointer exceptions may be caused because o1 and o2 are not instantiated completely.

Double check lock + volatile

The above method is actually very simple. Add a volatile keyword to instance to prevent program exceptions caused by reordering.

private volatile static test2 instance;

Internal class (Holder) mode

The holder method makes use of the characteristics of class loading. We look at the code directly.

public class test3 {
	private test3() {
		System.out.println("Class is instantiated");
	}

	/**
	 * Using the internal class method will not load actively, but only when the main class is used
	 */
	private static class Holder {
		private static test3 instance = new test3();
	}

	/**
	 * Provide external call
	 * @return
	 */
	public static test3 getInstance() {
		return Holder.instance;
	}

}

Summary: We found that there is no instance in test3, but it is put into a static internal class and loaded in a hungry Chinese style. But in fact, this is not a hungry man. Because the static internal class will not be loaded actively, it will be loaded only when the main class is used, which ensures that when the program runs, it will not directly create an instance and waste memory. When we actively reference the Holder, we will create an instance, so as to ensure lazy loading.

Enumeration mode

The enumeration method to realize the singleton mode is pushed by the author of Effective Java. The enumeration type is not allowed to be inherited. It is also thread safe and can only be initialized once. However, the enumeration type cannot be lazy loaded. For example, the following code will be instantiated immediately once the static method is used.

Example:

public enum test4 {
	INSTANCE;

	test4() {
		System.out.println("Class is instantiated");
	}
	public static test4 getInstance() {
	    return INSTANCE;
	}
}

Added by jesse24 on Wed, 19 Jan 2022 21:17:20 +0200