[design mode] Chapter 3 single example mode

Posted by chinto09 on Sun, 19 Dec 2021 23:40:07 +0100

Chapter 3 single example mode

1, Introduction

Singleton Pattern is a creation mode, which provides the best way to create objects. This mode involves a single class, which is responsible for creating its own objects and ensuring that only a single object is created. This class provides a way to access its unique object, which can be accessed directly without instantiating the objects of this class

be careful:

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

The purpose of singleton mode is to ensure that a class has only one instance and provide a global access to it. It solves the problem that a globally used class needs to be created and destroyed frequently, and can save system resources

2, Realize

1. Hungry Han style

Class is loaded into memory to instantiate a singleton, which is simple and practical

The constructor is private and cannot be called in other classes. The instance can only be obtained through the getInstance method

public class Singleton{
	private static final Singleton INSTANCE;

	private Singleton(){}

	public static Singleton getInstance(){
		return INSTANCE;
	}
}

The disadvantage is that no matter whether an instance needs to be created or not, as long as the class is loaded (Class.forName), it will be instantiated and generate garbage objects

2. Double check lock

It is instantiated only when in use, which achieves the purpose of on-demand initialization, but it brings the problem of thread insecurity

public class Singleton{
	private static Singleton INSTANCE;

	private Singleton(){}

	public Singleton getInstance(){
		if(INSTANCE == null)
			INSTANCE = new Singleton();
		return INSTACNE;
	}

	public static void main(String[] args){
		for(int i = 0; i < 100; i++)
			new Thread(() -> 
				System.out.println(Singleton.getInstance().hashCode())
			).start();
		}
	}
}

Suppose that both threads A and B use this singleton class, A first calls the getInstance method, judges that INSTANCE is empty, and enters the if code block, but there is no new
At this time, thread B also calls the getInstance method. Since A has not been new, thread B judges that INSTANCE is empty and enters the if code block
In this way, both A and B can create A new instance. In this way, A and B must not get the same instance, so A single instance cannot be guaranteed

We can lock the getInstance method to solve the thread safety problem

public class Singleton{
	private static Singleton INSTANCE;

	private Singleton(){}

	public static synchronized Singleton getInstance(){
		if(INSTANCE == null)
			INSTANCE = new Singleton();
		return INSTANCE;
	}
}

Although the thread safety problem is solved, it also leads to the decrease of efficiency by judging whether the method holds a lock every time it is called

We can improve efficiency by reducing the range of synchronized code blocks

public class Singleton{
	private static Singleton INSTANCE;

	private Singleton(){}
	
	public static Singleton(){
		if(INSTANCE == null){
			sychronized(Singleton.class){
				INSTANCE = new Singleton();
			}
		}
		return INSTANCE;
	}
}

However, it is meaningless to simply lock the instantiation. Threads A and B can enter the if code block no matter whether they hold the lock or not. Until the lock is released after the lock holder is new, the other party can still obtain the lock to create A second instance

Therefore, double inspection lock is required

Double check lock, that is, after obtaining the lock, judge whether it is empty in the synchronization code block

public class Singleton{
	private static Singleton INSTANCE;

	private Singleton(){}

	public static Singleton getInstance(){
		if(INSTANCE == null){
			sychronized(Singleton.class){
				if(INSTANCE == null)
					INSTANCE = new Singleton();
			}
		}
		return INSTANCE;
	}
}

However, we also ignore the problem of instruction reordering

Instance in Java = new singleton(); It will be compiled into the following JVM instructions by the compiler:

memory = allocate(); //1. Memory address of allocation object
ctorINSTANCE(memory);//2. Initialization object
INSTANCE = memory; //3. Set instance to point to the memory address just allocated

However, the order of these instructions is not invariable. They may be rearranged into the following order through JVM and CPU optimization

memory = allocate();		//1. Allocate the memory space of the object
INSTANCE = memory;			//3. Set INSTANCE to point to the memory address just allocated
ctorInstance(memory);		//2. Initialization object

When thread A finishes executing 1 and 3, the INSTANCE object has not completed initialization, but it no longer points to null. At this time, if thread B preempts CPU resources, the result of executing the judgment statement will be false, thus returning an uninitialized INSTANCE object

This requires adding a volatile modifier to ensure that the instructions are not reordered and visible. For details, see Chapter 4 Java Memory Model in [JVM]

public class Singleton{
	private static volatile Singleton INSTANCEl;
	
	private Singleton(){}

	public static Singleton getInstance(){
		if(INSTANCE == null){
			sychronized(Singleton.class){
				if(INSTANCE == null){
					INSTANCE = new Singleton();
				}
			}
		}
		return INSTANCE;
	}
}

3. Static internal class

When loading an external class, the internal class will not be loaded, and the internal class will only be loaded by the JVM once, so this can also be a way to implement the singleton mode

public class Singleton{
	private static class SingletonHolder{
		private static final Singleton INSTANCE = new Singleton();
	}
		
	private Singleton(){}

	public static Singleton getInstance(){
		return SingletonHolder.INSTANCE;
	}
}

4. Enumeration

This implementation has not been widely used, but it is the best way to implement the singleton pattern and is more concise

public enum Singleton{
	INSTANCE;
}

3, Improvement

1. Prevent the destruction of singleton mode by reflection, cloning and serialization

Double check locks and static inner classes both implement lazy loading and thread safety, but do you think it's over? There are still three ways to destroy the singleton mode, which is why enumeration is the best way to realize the singleton mode, because it is naturally immune to these three kinds of destruction. First, enum has no constructor, so it cannot create instances through reflection; Secondly, enum prohibits overriding clone(); Furthermore, enum automatically supports the serialization mechanism to absolutely prevent multiple instantiations

Even if there are many ways to destroy, we can solve it

  • Prevent reflection damage
    First, define a global variable switch isfirstcrete, which is on by default
    Change its state to off when it is first loaded
  • Prevent clone corruption
    Override clone() to return the singleton object directly
  • Prevent serialization corruption
    Add readResolve() to return the Object object

Take double check lock as an example:

public class Singleton  implements Serializable,Cloneable{
    private static final long serialVersionUID = 6125990676610180062L;
    private static Singleton INSTANCE;
    private static boolean isFristCreate = true;

    private Singleton(){
        if (isFristCreate) {
            synchronized (Singleton.class) {
                if (isFristCreate) {
                    isFristCreate = false;
                }
            }
        }else{
            throw new RuntimeException("It has been instantiated once and cannot be instantiated again");
        }
    }
   
    public  static Singleton getInstance(){
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
    @Override
    protected Singleton clone() throws CloneNotSupportedException {
        return INSTANCE;
    }
    private Object readResolve() {
        return INSTANCE;
    }
}

Destruction test case:

public class DestroySingleton {
    public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //Get through getInstance()
        Singleton singleton = Singleton.getInstance();
        System.out.println("singleton of hashCode: " + singleton.hashCode());

        //Obtained by cloning
        Singleton clone = (Singleton) Singleton.getInstance().clone();
        System.out.println("clone of hashCode: " + clone.hashCode());

        //Get by serialization and deserialization
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(Singleton.getInstance());
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        Singleton serialize = (Singleton)ois.readObject();
        System.out.println("serialize of hashCode: " + serialize.hashCode());

        //Get by reflection
        Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton reflex = constructor.newInstance();
        System.out.println("reflex of hashCode: " + reflex.hashCode());
    }
}

Operation results:

"C:\Program Files\Java\jdk1.8.0_221\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2021.1\lib\idea_rt.jar=65367:C:\Program Files\JetBrains\IntelliJ IDEA 2021.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_221\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\rt.jar;C:\Users\admin\Desktop\project\testProjct\Temp\target\classes" com.sisyphus.singleton.DestroySingleton
singleton of hashCode: 460141958
clone of hashCode: 460141958
serialize of hashCode: 460141958
Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at com.sisyphus.singleton.DestroySingleton.main(DestroySingleton.java:36)
Caused by: java.lang.RuntimeException: It has been instantiated once and cannot be instantiated again
	at com.sisyphus.singleton.Singleton.<init>(Singleton.java:18)
	... 5 more

The process has ended with exit code 1

The instance obtained by cloning and deserialization is the same instance obtained by getInstance(), but the instantiated instance cannot be obtained through reflection

Topics: Java Design Pattern