In the interview, many people will be asked: name the three design patterns you know best, which design patterns you use in daily development, etc. This is the first of several articles to look at design patterns. This is the most common pattern, the singleton pattern.

What is a singleton

The Singleton Pattern, as the name implies, guarantees that there is only one instance of a class and provides an access point globally.

When implementing a singleton, to ensure that a class has only one instance, it cannot provide a public constructor, allowing other classes to create instances, and the corresponding variable must also be static and initialized only once at load time. Also, to be globally accessible, you need to provide a static public method to access it.

There are many specific implementation methods. For different scenarios, different methods should be selected, such as whether thread safety is required and whether lazy loading is required. So let’s see.

Hungry (thread-safe)

Based on the above description of the singleton pattern implementation, it is easy to think of the following implementation:

public class Singleton1 {

    private static Singleton1 instance = new Singleton1();

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

This way, the instance is created when the class is first loaded. This is called hangry, meaning that when you want to use an instance, you can get it right away, without waiting.

This way, the JVM keeps its thread safe. However, this approach can be a resource drain because the instance may not be needed at all and may be loaded unnecessarily.

Lazy (not thread-safe)

The above approach is instantiated at class load time and may result in unnecessary loading. Then we can instantiate it when it is actually accessed, so we can write as follows:

public class Singleton2 {

    private static Singleton2 instance;

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

In the getInstance method, initialization is performed on the first access because there is no initialization, and on subsequent accesses, the instance is returned directly. This is known as lazy style, that is, it does not create the instance ahead of time, but delays it until it is first accessed.

But lazy has thread safety issues, as shown below:

In a multi-threaded scenario, if two threads enter an IF statement at the same time, each thread creates an object, and when both threads exit from the IF, they create two different objects.

Lazy (thread-safe)

Since common sloth has thread-safety issues, lock the method that creates the object:

public class Singleton3 {

    private static Singleton3 instance;

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

This works well in multi-threaded scenarios, but with lazy loading. However, the efficiency of synchronized method is low because it locks the whole method. So, if you are smart, you can easily think of using synchronized method blocks to reduce the granularity of locking.

The following two methods do reduce the granularity of locking, but they do not guarantee thread-safety:

synchronized (Singleton4.class) {
    if (instance == null) {
        instance = newSingleton4(); }}Copy the code

Due to a problem with specifying resort, this will be explained in more detail later in the introduction of double checklock.

if (instance == null) {
    synchronized (Singleton4.class) {
        instance = newSingleton4(); }}Copy the code

If synchronized is appended to an if statement, it is no different from plain lazy synchronized. If two threads enter the if statement separately, both threads will instantiate it twice, even though they will lock it.

Double check lock (thread safety)

Therefore, the double check lock method is introduced, which can determine whether the object is instantiated first, if there is no lock, and then after the lock, determine whether the object is instantiated again, if there is still no instantiation, then the object is instantiated.

The complete code for this is as follows:

public class Singleton5 {
    
    private static volatile Singleton5 instance;
    
    private Singleton5(a) {}public static Singleton5 getInstance(a) {
        // If it has been instantiated, it returns directly, without locking, to improve performance
        if (instance == null) {
            synchronized (Singleton5.class) {
                // Check again to ensure thread safety
                if (instance == null) {
                    instance = newSingleton5(); }}}returninstance; }}Copy the code

As you can see, there are two if statements before and after a synchronized statement, which is called a double check lock.

Using volatile

In fact, if only double check, still does not guarantee thread-safety issues. Instance = new Singleton5(); This code.

Although the code has only one sentence, it is actually executed in three steps in the JVM:

  1. forinstanceAllocate memory space;
  2. rightinstanceInitialize;
  3. willinstancePointer to allocated memory address;

However, since the compiler or processor may reorder the instructions, the order of execution may become 1->3->2. This is not a problem in a single-threaded environment, but can cause a thread to acquire an instance that has not yet been initialized in a multi-threaded environment.

For example, after step 1 and step 3, thread B calls the getInstance() method, determines that instance is not empty, and returns instance. But instance has not yet been initialized.

Therefore, instance needs to be volatile to prevent reordering of the compiler’s instructions and to run properly in a multithreaded environment.

Static inner class (thread-safe)

The current approach of double check locks looks good, using lazy loading, while ensuring thread safety, locking granularity is relatively small, and efficiency is good. Is there another way?

That is to use static inner class implementation, take a look at its implementation:

public class Singleton6 {

    private Singleton6(a) {}private static class InnerSingleton {
        private static final Singleton6 INSTANCE = new Singleton6();
    }

    public static Singleton6 getInstance(a) {
        returnInnerSingleton.INSTANCE; }}Copy the code

In this implementation, when the external Singleton6 class is loaded, the static InnerSingleton class is not loaded.

Instead, the inner class is loaded to instantiate INSTANCE only when the getInstance method is called to access the static variable of the class. And the JVM can ensure that INSTANCE can only be instantiated once, meaning that it is also thread-safe.

Enumeration (thread-safe)

Also, using enumerations is a good way to implement singletons, and the code is very simple:

public enum Singleton6 {
    INSTANCE();

    Singleton6() { }
}
Copy the code

In the implementation of enumeration, a class is defined as final, its enumeration values are defined as static final, and initialization of the enumeration values is placed ina static statement block. So, the object is instantiated when the class is first loaded, which not only avoids thread-safety issues, but also avoids the deserialization of singletons mentioned below.

Singletons and serialization

Now let’s see if an object can be serialized and deserialized without a singleton.

Add Serializable interface to Singleton5 and test:

public class SingletonTest {

    public static void main(String[] args) {
        Singleton5 instance1 = Singleton5.getInstance();
        try ( ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile")) ){
            oos.writeObject(instance1);
        } catch (IOException e) {
            e.printStackTrace();
        }

        Singleton5 instance2 = null;
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("tempFile"))) ){
            instance2 = (Singleton5) ois.readObject();
        } catch(IOException | ClassNotFoundException e) { e.printStackTrace(); } System.out.println(instance1 == instance2); }}// false
Copy the code

As you can see, antisequencing Singleton5 yields a new object, thus breaking Singleton5’s singleton.

We can add a readResolve() method to the Singleton5 class and specify the generation strategy for the object to be returned:

public class Singleton5 implements Serializable {

    private static volatile Singleton5 instance;

    private Singleton5(a) {}public static Singleton5 getInstance(a) {
        if (instance == null) {
            synchronized (Singleton5.class) {
                if (instance == null) {
                    instance = newSingleton5(); }}}return instance;
    }

    // Add the readResolve method
    private Object readResolve(a) {
        returninstance; }}Copy the code

The invokeReadResolve method of the ObejctStreamClass can be seen in the call stack of the readObject method using the debug method to view the source code:

If the readResolve method is defined, it is called through reflection to generate the object according to the specified policy.

What are some good singleton practices

JDK#Runtime

This class is used to get the environment in which the application is running. You can see that this is a hungrier singleton.

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime(a) {
        return currentRuntime;
    }

    private Runtime(a) {}}Copy the code

Spring#Singleton

When defining a Bean in Spring, you can specify whether it is singleton or multi-singleton (the default is singleton) :

@Scope("singleton")
Copy the code

View its source code, singleton mode implementation is as follows:

public abstract class AbstractFactoryBean<T>
		implements FactoryBean<T>, BeanClassLoaderAware.BeanFactoryAware.InitializingBean.DisposableBean {

    private T singletonInstance;
    
    @Override
	public void afterPropertiesSet(a) throws Exception {
	    // Scan configurations in singleton mode
	    // Initialized is set to true
		if (isSingleton()) {
			this.initialized = true;
			// Call the subclass method to create the object
			this.singletonInstance = createInstance();
			this.earlySingletonInstance = null; }}@Override
	public final T getObject(a) throws Exception {
		if (isSingleton()) {
			return (this.initialized ? this.singletonInstance : getEarlySingletonInstance());
		}
		else {
			returncreateInstance(); }}}Copy the code

The resources

  • Hollis: Design pattern (II) — singleton pattern
  • CyC2018/CS-Notes: Design patterns
  • The harder you work, the luckier you get