Singleton mode notes

Keywords: Java Singleton pattern

Hungry Han style

Initialize the object at the beginning. Whether it is used or not, this class allocates space, initializes the object, and points the object to space

Disadvantages: waste of space

public class HungerMan {

	private HungerMan() {

	}

	private final static HungerMan hungerMan = new HungerMan();

	private static HungerMan getInstance() {
		return hungerMan;
	}

}

Lazy style

Use to initialize objects to save space

Disadvantages: it is only used for single thread. If the program wants to be concurrent, it needs to use double check lock

public class LazyMan {
	private LazyMan() {	
	}
	
	private static LazyMan lazyMan ;
	
	private static LazyMan getInstance() {
		//The object is initialized only when it is empty
		if(lazyMan==null) {
			lazyMan = new LazyMan();
		}
		return lazyMan;
	}
	
	public static void main(String[] args) {
		for(int i=0;i<10;i++) {
			new Thread(()->{
				LazyMan.getInstance();
			}).start();
		}	
	}
}

Double check lock: add a layer of lock to the above code and use thread synchronization to synchronize

analysis:

  • lazyMan = new LazyMan(); Not atomic operation
    There are actually three steps:
    *Allocate memory space
    *Initialize constructor instance object
    *Point the object to the space
  • Instruction rearrangement occurred
    *The startup thread may execute 123 or 132
    *If thread A executes 132, thread B first judges that the object is not null. Since the object execution order is not 123, the returned object is not initialized
    Solution: add volatile
public class LazyMan {
	//Private constructor
	private LazyMan() {
		System.out.println(Thread.currentThread().getName()+"ok");
	}
	
	//Plus volatile atomic operations
	private volatile static LazyMan lazyMan ;
		
	private static LazyMan getInstance() {	
		/**
		 * Use double check lock
		 */
		if(lazyMan==null) {
			synchronized (LazyMan.class) {
				if(lazyMan==null) {
					lazyMan = new LazyMan();
				}
			}
		}				
		return lazyMan;
	}
	
	public static void main(String[] args) {
		//1.
		for(int i=0;i<10;i++) {
			new Thread(()->{
				LazyMan.getInstance();
			}).start();
		}
		
		
		
	}
}

Static inner class

The static internal class is an independent layer, and the effect is the same as that of double check lock

package workspace.Singleton mode;

//Static inner class
public class Inside {
	private Inside() {
		System.out.println(Thread.currentThread().getName() + "ok");
	}
	private static Inside getInstance() {
		return Man.INSIDE;
	}
	private static class Man {
		private final static Inside INSIDE = new Inside();
	}

	public static void main(String[] args) {
		for (int i = 0; i < 10; i++) {
			new Thread(() -> {
				Inside.getInstance();
			}).start();
		}
	}
}

Enumeration and reflection

Except enumeration, all methods are unsafe and can be destroyed by reflection. However, reflection is artificially destroyed. Using double retrieval or static internal classes is enough.

Reflection mechanism

The null parameter function can be constructed directly by reflection

public static void main(String[] args) throws Exception{
		//Normal instantiation
		LazyMan lazyMan2 = LazyMan.getInstance();
		System.out.println(lazyMan2);
		//Reflection failure
		Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
		constructor.setAccessible(true);
		LazyMan lazyMan = constructor.newInstance();
		System.out.println(lazyMan);

 	}

The results of the two objects are different, and the reflection is successfully destroyed

Solution: lock the null parameter function

private LazyMan() {
		synchronized (LazyMan.class) {
			if(lazyMan!=null) {
				throw new RuntimeException("Do not destroy the view by reflection");
			}
			
		}
	}

Crack:

Both initialization objects get null parameter objects for reflection

combat

Flag key can be added to prevent reflection

However, this is still unsafe, because you can know jingQing by decompiling the code through jad, and modify it back to false. Reflection can still be broken.

Enumeration non reflective destruction

Through jad decompile enumeration, it can be found that there is no empty parameter constructor in enumeration, so enumeration cannot be destroyed.
Write test code

The returned result is not

It is not the result of reflection failure we want
Decompile

  • javap -p decompile results show that our constructor has parameters.

  • jad decompile source code is a parametric constructor.

Destroy it again

Reflection cannot break enumeration singleton mode

Posted by GrexP on Sat, 04 Dec 2021 17:35:35 -0800