This is the 5th day of my participation in the August More Text Challenge

Model is introduced

The point of the schema is to ensure that a class has only one instance and the same global access point

Singleton Pattern is a common and very simple design Pattern. We can use Singleton Pattern design when we require only one instance in the program. Objects such as registries, usage logs, and printer drivers can only have one instance, and creating multiple objects can cause a series of problems.

Java uses the new keyword to create new objects in the heap. To have a unique object, we need to ensure that the class is only new once, and then we get that object every time we use that class object. Imagine that when the constructor of a class is privatized, the only method that can call the constructor exists only inside the class, thus ensuring that no object of the class is created outside it. Since this object is the only object of the class and is used from the time the class is instantiated to the time it is unloaded, we can declare a static instance object in the class so that the object will have space in the method area when the class is loaded and will be released after the class is unloaded.

Two types of singleton patterns

The hungry type

Hunchstyle instantiates the singleton in advance and returns it directly when needed

public class SingletonWithHungry extends AbstractSingleton{ private static final SingletonWithHungry INSTANCE = new SingletonWithHungry(); private SingletonWithHungry() { } public static SingletonWithHungry getInstance() { return INSTANCE; }}Copy the code

LanHanShi

Lazy style avoids the waste of resources caused by early instantiation and completes the instantiation on the first fetch

public class SingletonWithDoubleCheck extends AbstractSingleton { private static SingletonWithDoubleCheck singletonWithDoubleCheck; private SingletonWithDoubleCheck() {} public static SingletonWithDoubleCheck getInstance() { if (singletonWithDoubleCheck == null) { singletonWithDoubleCheck = new SingletonWithDoubleCheck(); } return singletonWithDoubleCheck; }}Copy the code

To improve the

Slob style under multithreading

When multithreading is enabled, two threads get the instantiated object at the same time. The first thread checks instance == null, and the second thread immediately enters the statement. Both threads get true, which causes the singleton to fail. The instability of multithreading is mainly due to the lazy loading of the instantiation, if we can instantiate the object outside the use of the thread, do not create when using, but just return the existing instance object that is hanhantype, so that we can avoid multithreading caused by the multi-object exception. However, the direct instantiation approach misses the resource saving benefits of delayed instantiation.

In Java, to solve the problem of thread conflict, synchronized thread locks are commonly used for thread synchronization. Change getInstance to a thread-synchronized method with this as its lock object to ensure that the method is executed by one thread and not by other threads at the same time. But once one thread enters the method, all the other threads must wait, even though the object has been instantiated. This can block threads for too long and affect system performance.

public static synchronized SingletonWithDoubleCheck getInstance() {
  if (singletonWithDoubleCheck == null) {
    singletonWithDoubleCheck = new SingletonWithDoubleCheck();
  }
  return singletonWithDoubleCheck;
}
Copy the code

Use double check locks

To improve thread-safe sloth, we need to make the getInstance() method non-blocking for all threads, so we should only lock objects when they are instantiated. Note that if Thread A and Thread B enter the first if statement for the first time, and if there is no second if statement, both threads will new SingletonWithDoubleCheck(). Therefore, a double check lock is required.

Also note that the instance object must be decorated with the volatile keyword for double check locks. SingletonWithDoubleCheck = new singletonWithDoubleCheck () this code is actually executed in three steps:

  1. Allocate memory space in the Heap for Singleton with Double heck
  2. Initialize singletonWithDoubleCheck
  3. Point singletonWithDoubleCheck to the allocated memory address

However, due to the reordering nature of JVM instructions, the order of execution may change to 1, 3, and 2, which in multithreading may result in one thread getting an uninitialized instance, such as T2 calling getInstance() when T1 has just executed 1 and 3. This is the famous DCL failure problem, the use of the volatile keyword in double check locks was introduced by Doug Lea and made possible by improvements in the Java memory model.

Public class SingletonWithDoubleCheck extends AbstractSingleton {** ** Private volatile static SingletonWithDoubleCheck singletonWithDoubleCheck; private SingletonWithDoubleCheck() {} public static SingletonWithDoubleCheck getInstance() { if (singletonWithDoubleCheck == null) { synchronized (SingletonWithDoubleCheck.class) { if (singletonWithDoubleCheck == null) { singletonWithDoubleCheck = new SingletonWithDoubleCheck(); } } } return singletonWithDoubleCheck; }}Copy the code

Static inner class implementation

The characteristic of a static inner class is that the inner class is not loaded immediately when the external class is loaded, so the object of the inner class is not instantiated immediately. In this way, we can initialize the object in the static inner class so that there is only one instance object, the object is initialized lazily, and is thread safe by the JVM.

public class SingletonWithStaticInnerClass extends AbstractSingleton{ private SingletonWithStaticInnerClass() {} /** * A static inner class * / private static class SingletonInstance {private static final SingletonWithStaticInnerClass INSTANCE = new SingletonWithStaticInnerClass(); } public static SingletonWithStaticInnerClass getInstance() { return SingletonInstance.INSTANCE; }}Copy the code

Its thread-safe implementation is related to the design of the JVM, which ensures that a class’s () methods can be locked and synchronized correctly in a multithreaded environment. If multiple threads initialize a class at the same time, only one thread will execute the class’s () method, and all the other threads will have to wait. If a class’s () method takes too long, it can cause multiple threads to block, but often insidiously. It can be seen that INSTANCE is thread-safe in the creation process, so the singleton in the form of static inner class can ensure thread-safe and uniqueness of the singleton, but also delay the instantiation of the singleton. However, the resulting object cannot be passed parameters from outside.

Enumeration implementation

Enum is implemented by Class, which can include member variables and member methods, and has and only private constructors to prevent additional constructs, similar to singleton Class constructs in SingletonPattern, so the SingletonPattern can be implemented directly with Enum. Using enumerations, you can serialize and deserialize the object many times and still get the same instance object.

public enum  SingletonWithEnum {
    INSTANCE;

    SingletonWithEnum(){}
}
Copy the code

Reflection and deserialization attacks

Reflection attack

Let’s test whether a single example mode can resist reflection attack, first implement a reflection attack tool class

/** * reflection attacker */ public class SingleReflectAttacker */ ** * reflection attacker * @param classType Use this class to get an object * @param instance object Public static Boolean attackWithReflect(Class<? > classType, AbstractSingleton instance) { Constructor<? > c; Object instanceFromReflect = null; try { c = classType.getDeclaredConstructor(); c.setAccessible(true); instanceFromReflect = c.newInstance(); } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { e.printStackTrace(); } return return instanceFromReflect ! = null && instance ! = instanceFromReflect; } public static boolean attackWithReflect(Class<? > classType, SingletonWithEnum instance) { attackWithReflectMethod1(classType); attackWithReflectMethod2(classType); return true; } public static void attackWithReflectMethod1(Class<? > classType) { try { Class.forName(classType.getName()).newInstance(); } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { e.printStackTrace(); } } public static void attackWithReflectMethod2(Class<? > classType) { try { Constructor[] constructors = classType.getDeclaredConstructors(); for (Constructor c : constructors) { c.setAccessible(true); c.newInstance(); } } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { e.printStackTrace(); }}}Copy the code

First test hanky-hanky

boolean result; / * * * * ordinary hungry type/result = SingleReflectAttacker attackWithReflect (SingletonWithHungry. Class, SingletonWithHungry.getInstance()); System.out.println(" + result ");Copy the code

The results of

【 hungry 】 reflex attack result: true

This means that the normal hunk cannot block reflex attacks

How to improve

Reflection creates a new object by calling the constructor, so we add a layer of judgment to the constructor. If the object is not NULL, we want to create a new object, and throw an exception

/ * * * to improve the constructor * / private SingletonWithHungrySafe () {synchronized (SingletonWithHungrySafe. Class) {if (the INSTANCE! = null) {throw new RuntimeException(" singleton pattern broken "); }}}Copy the code

Use reflection attack test

result = SingleReflectAttacker.attackWithReflect(SingletonWithHungrySafe.class, SingletonWithHungrySafe.getInstance()); System.out.println(" result of reflex attack: "+ result);Copy the code

The results of

4. Safe hungry The result of a reflex attack: false 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.fafnir.SingletonPattern.SingleReflectAttacker.attackWithReflect(SingleReflectAttacker.java:25) at com.fafnir.SingletonPattern.SingletonTest.main(SingletonTest.java:18) Caused by: java.lang.RuntimeException: The singleton pattern is destroyed at com. Fafnir. SingletonPattern. SingletonWithHungrySafe. (SingletonWithHungrySafe. Java: 19)… 6 more

This points us to ways to avoid reflection attacks: avoid calling the constructor repeatedly through a state field, as in a double checklock

public class SingletonWithDoubleCheckSafe extends AbstractSingleton { private volatile static SingletonWithDoubleCheckSafe singletonWithDoubleCheck; /** * private static Boolean flag; Private SingletonWithDoubleCheckSafe () {synchronized (SingletonWithDoubleCheckSafe. Class) {/ / if the flag to true has been constructed over the if (! flag) { flag = true; } else {throw new RuntimeException(" singleton pattern broken "); }}} //... }Copy the code

In a static inner class

public class SingletonWithStaticInnerClass extends AbstractSingleton{ private static boolean flag; private SingletonWithStaticInnerClass() { synchronized (SingletonWithDoubleCheckSafe.class) { if (! flag) { flag = true; } else {throw new RuntimeException(" singleton pattern broken "); }}} //... }Copy the code

Deserialization attack

Implement an anti-sequence attack tool class

public class SingleDeserializeAttacker { public static boolean attackWithDeserialize(AbstractSingleton instance) { String path = Constants.PATH + instance.toString(); AbstractSingleton instanceFromDeserialize; ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(path)); outputStream.writeObject(instance); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(path)); instanceFromDeserialize = (AbstractSingleton) inputStream.readObject(); } catch (IOException | ClassNotFoundException e) { System.out.println(e); return false; } return instance ! = instanceFromDeserialize; } public static boolean attackWithDeserialize(SingletonWithEnum instance) { String path = Constants.PATH + instance.toString(); SingletonWithEnum instanceFromDeserialize = null; try { ObjectOutputStream outputStream = new ObjectOutputStream( new FileOutputStream(path)); outputStream.writeObject(instance); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(path)); instanceFromDeserialize = (SingletonWithEnum) inputStream.readObject(); } catch (ClassNotFoundException | IOException e) { System.out.println(e); return false; } return instance ! = instanceFromDeserialize; }}Copy the code

Deserialization attack hangry

result = SingleDeserializeAttacker.attackWithDeserialize(SingletonWithHungry.getInstance()); System.out.println(" deserialization result: "+ result);Copy the code

The results of

Deserialization attack result: true

How to improve: Define the readResolve method, see serialization and deserialization of singleton attacks

public class SingletonWithHungrySafe extends AbstractSingleton { // ... /** * */ private Object readResolve() { return INSTANCE; }}Copy the code

The model structure

The singleton pattern is a simple and practical design pattern that has the following main components when used to obtain singletons

  • A static instance object or reference
  • Private constructor
  • Static getInstance method (global access point)

Pattern analysis

The purpose of the singleton pattern is to ensure that a class has only one instance and to provide a global access point to it. The Singleton pattern contains only one role, the Singleton class. The singleton class has a private constructor that ensures that users cannot instantiate it directly with the new keyword. In addition, the pattern contains a static private member variable and a statically public factory method that checks for the existence of the instance and instantiates itself, then stores it in the static member variable to ensure that only one instance is created. # # advantages

  • Provides controlled access to a unique instance. Because the singleton class encapsulates its unique instance, it has tight control over how and when customers access it and provides shared concepts for design and development teams.
  • Since only one object exists in system memory, system resources are saved, and the singleton pattern can definitely improve system performance for objects that need to be created and destroyed frequently.
  • A variable number of instances is allowed. The singleton pattern can be extended to obtain a specified number of object instances using a method similar to singleton control.

disadvantages

  • Because there is no layer of abstraction in the singleton pattern, it is difficult to extend the singleton class.
  • Singleton classes are too heavy on responsibilities and violate the “single responsibility principle” to some extent. Because the singleton class acts both as a factory, providing factory methods, and as a product, containing business methods that blend the creation of the product with the functionality of the product itself.
  • Abuse of singletons will bring some negative problems, for example, in order to save resources, the database connection pool object is designed as a singleton class, which may lead to too many programs sharing the connection pool object and connection pool overflow. Many object-oriented languages (such as Java and C#) now provide automatic garbage collection. Therefore, if an instantiated object is not used for a long time, the system will consider it garbage, destroy and recycle the resource, and re-instantiate the object the next time it is used, which will result in the loss of the object state.

Apply to the environment

  • The system only needs one instance object, if the system requires a unique sequence number generator, or if the resource consumption is too high to allow the creation of only one object.
  • A single instance of a customer invoking class is allowed to use only one public access point, and the instance cannot be accessed by any other means than that public access point.
  • The singleton pattern should only be used when only one instance of a class is required in a system. Conversely, if several instances of a class can coexist, the singleton pattern needs to be modified to become a multi-instance pattern.