Singleton mode
Definition: Ensure that a class has only one instance and provide a global access point
Type: Creative
Use scenarios:
To ensure there is absolutely one instance in any case
Advantage:
Only one instance in memory reduces memory overhead
Avoid multiple occupation of resources
Set up global access points and strictly control access
Disadvantages:
No interface, difficult to extend
A key:
Private Constructor
Thread Security
Delayed Loading
Serialization and deserialization security
Prevent reflection
Lazy:
public class LazySingleton { private static LazySingleton lazySingleton = null; private LazySingleton(){} public static LazySingleton getInstance() { if (lazySingleton == null) { lazySingleton = new LazySingleton(); } return lazySingleton; } }
Multithreaded debug:
public class T implements Runnable{ @Override public void run() { LazySingleton lazySingleton = LazySingleton.getInstance(); System.out.println(Thread.currentThread().getName() + " " + lazySingleton); } } public class Test { public static void main(String[] args) { Thread t1 = new Thread(new T()); Thread t2 = new Thread(new T()); t1.start(); t2.start(); System.out.println("program end"); } }
Result: In the case of multi-threaded debug manual intervention breakpoint, the object acquired in thread 0 and thread 1 singleton mode is not the same, and there are hidden dangers in the program
Connected to the target VM, address: '127.0.0.1:52373', transport: 'socket' Thread-0 main.java.creational.singleton.LazySingleton@31309637 Thread-1 main.java.creational.singleton.LazySingleton@42fc6f97 program end
After improvement:
public class LazySingleton { private static LazySingleton lazySingleton = null; private LazySingleton(){} // Evolution public synchronized static LazySingleton getInstance() { if (lazySingleton == null) { lazySingleton = new LazySingleton(); } return lazySingleton; } }
Because of the direct locking in the static method, the object being locked is the current class, which has some overhead. Let's look at another way - double check:
public class LazyDoubleCheckSingleton { private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null; private LazyDoubleCheckSingleton() {} public static LazyDoubleCheckSingleton getInstance() { if (lazyDoubleCheckSingleton == null) { synchronized (LazyDoubleCheckSingleton.class) { if (lazyDoubleCheckSingleton == null) { lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton(); // 1. Allocate memory space // 3. Point to memory space // 2. Initialize Objects // Reordering of instructions } } } return lazyDoubleCheckSingleton; } }
Since instruction reordering is involved in the code, there are two ways to resolve it, one is to prohibit reordering, the other is to allow reordering but not allow other threads to see it.
The first solution prohibits reordering, using the volatile keyword:
public class LazyDoubleCheckSingleton { private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null; private LazyDoubleCheckSingleton() {} public static LazyDoubleCheckSingleton getInstance() { if (lazyDoubleCheckSingleton == null) { synchronized (LazyDoubleCheckSingleton.class) { if (lazyDoubleCheckSingleton == null) { lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton(); // 1. Allocate memory space // 3. Point to memory space // 2. Initialize Objects // Reordering of instructions } } } return lazyDoubleCheckSingleton; } }
The second solution, static internal classes:
public class StaticInnerClassSingleton { private StaticInnerClassSingleton(){} private static class InnerClass { private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton(); } public static StaticInnerClassSingleton getInstance() { return InnerClass.staticInnerClassSingleton; } }
Singleton pattern - Hungry Han style:
public class HungrySingleton { private final static HungrySingleton hungrySingleton; static { hungrySingleton = new HungrySingleton(); } private HungrySingleton() {} public static HungrySingleton getInstance() { return hungrySingleton; } }
Serialization and deserialization issues:
Test class:
public class Test { public static void main(String[] args) throws IOException, ClassNotFoundException { HungrySingleton instance = HungrySingleton.getInstance(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file")); oos.writeObject(instance); File file = new File("singleton_file"); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); HungrySingleton newInstance = (HungrySingleton) ois.readObject(); System.out.println(instance); System.out.println(newInstance); System.out.println(instance == newInstance); } }
Test class runtime error:
Connected to the target VM, address: '127.0.0.1:53906', transport: 'socket' Exception in thread "main" java.io.NotSerializableException: main.java.creational.singleton.HungrySingleton at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) at main.java.creational.singleton.Test.main(Test.java:16) Disconnected from the target VM, address: '127.0.0.1:53906', transport: 'socket' Process finished with exit code 1
HungrySingleton is required to implement the serialization interface
Run again as follows: instance and newInstance are not the same object, and the singleton is destroyed after serialization and deserialization
main.java.creational.singleton.HungrySingleton@7a81197d main.java.creational.singleton.HungrySingleton@4769b07b false Process finished with exit code 0
Solution: Add readResolve() to HungrySingleton. This article only provides a solution. See the source code for details
public class HungrySingleton implements Serializable { private final static HungrySingleton hungrySingleton; static { hungrySingleton = new HungrySingleton(); } private HungrySingleton() {} public static HungrySingleton getInstance() { return hungrySingleton; } private Object readResolve() { return hungrySingleton; } }
Reflection Attack:
Test class:
public class Test { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class objectClass = HungrySingleton.class; Constructor constructor = objectClass.getDeclaredConstructor(); // Release privileges on private constructors constructor.setAccessible(true); HungrySingleton instance = HungrySingleton.getInstance(); HungrySingleton newInstance = (HungrySingleton) constructor.newInstance(); System.out.println(instance); System.out.println(newInstance); System.out.println(instance == newInstance); } }
Run result:
main.java.creational.singleton.HungrySingleton@29453f44 main.java.creational.singleton.HungrySingleton@5cad8086 false Process finished with exit code 0
Reflection Attack Solution: Throw Runtime Exceptions in Private Constructors (also applicable to StaticInnerClassSingleton, static internal class, not LazySingleton)
public class HungrySingleton implements Serializable { private final static HungrySingleton hungrySingleton; static { hungrySingleton = new HungrySingleton(); } private HungrySingleton() { if (hungrySingleton != null) { throw new RuntimeException("Singleton mode prohibits reflection calls"); } } public static HungrySingleton getInstance() { return hungrySingleton; } private Object readResolve() { return hungrySingleton; } }