Focus on the singleton pattern

  1. Privatized constructor
  2. Ensuring thread safety
  3. Lazy loading
  4. Prevent serialization and deserialization from breaking singletons
  5. Defense Reflection attack singleton

Hangry singleton

A hungry singleton creates an instance when the singleton class is first loaded.

/ * * *@Description TODO
 * @Author Sitr
 * @Date2021/1/1 now *@Version1.0 * * /


public class HunGry {
	// Execute this statement when the class is loaded to create the instance.
    //final Indicates that the instance cannot be modified and can be prevented from being modified using reflection.
    private static  final  HunGry hungrySingleton;
    static{
    	hungrySingleton = new HunGry();
    }
    // Constructor is private. This prevents the outside world from using the build method, but only the classes created above.
    private HunGry(a){}
    // Provides a global access point, used by this method whenever the class is used by the outside world.
    public static  HunGry getInstance(a){
        // Return the instance
        returnhungrySingleton; }}Copy the code

Hunchman singletons, whether or not they are used, are initialized directly. Its disadvantage is that it will waste memory space. Because if the entire instance is not used, the class will still be created, which would be useless.

Hence the second singleton method:

Lazy singleton

Lazy singletons are singletons that are created when called externally.

/ * * *@Description TODO
 * @Author Sitr
 * @Date2021/1/1 so *@Version1.0 * * /


public class LazySingle {

    private static LazySingle lazySingle = null;

    private LazySingle(a){}

    public static LazySingle getInstance(a){
    // If lazySingle is empty, the instance is created; otherwise, the existing object is called directly.
        if(lazySingle == null){
            lazySingle = new LazySingle();
        }
        returnlazySingle; }}Copy the code

While lazy singletons solve the memory waste problem, they introduce a thread insecurity problem.

Why? Let’s take a simple example. ExectorThread.java

/ * * *@Description TODO
 * @Author Sitr
 * @Date 2021/1/1 23:00
 * @Version1.0 * * /


public class ExectorThread implements Runnable {

    public void run(a){
        LazySingle single = LazySingle.getInstance();
        System.out.println(Thread.currentThread().getName() + ":"+ single); }}Copy the code

LazyTest.java

/ * * *@Description TODO
 * @Author Sitr
 * @Date 2021/1/1 23:02
 * @Version1.0 * * /


public class LazyTest {

    public static void main(String[] args) {
        Thread t1 = new Thread(new ExectorThread());
        Thread t2 = new Thread(new ExectorThread());

        t1.start();
        t2.start();

        System.out.println("Exctor End"); }}Copy the code

Run:

Exctor End
Thread-0:com.edu.pattern.singleton.lazy.LazySingle@994e424
Thread-1:com.edu.pattern.singleton.lazy.LazySingle@781d0f57
Copy the code

As you can see, it creates two classes, causing thread-unsafe problems.

However, you can add synchronize to the getInstance method to lock it. Although synchronize has improved its performance considerably since JDK1.6, it still inevitably suffers from performance problems.

So we can try to double check the synchronize judgment, which is a double check lock.

/ * * *@Description TODO
 * @Author Sitr
 * @Date* 2021/1/1 brake@Version1.0 * * /

public class LazyDoubleCheck {
    private volatile static LazyDoubleCheck lazySingle = null;

    private LazyDoubleCheck(a){}

    public static LazyDoubleCheck getInstance(a){
        if(lazySingle == null) {synchronized (LazyDoubleCheck.class){// Synchronize inside
                if (lazySingle ==null) {// Add another judgment
                    lazySingle = newLazyDoubleCheck(); }}}returnlazySingle; }}Copy the code

Talk about instruction reordering

As we all know, when creating a class, it is converted to JVM instructions, which are executed in the following order:

  1. Allocate memory to this object
  2. Initialize an object
  3. Associate the initialized object with the memory address and copy it
  4. User’s First Access

Since CPU execution is preemptive, the second and third are confusing. Sometimes you might do 3 first and then 2. This problem can easily occur when renting multiple threads. To solve this problem, the common instruction is volatile.

Do we have a better lazy singleton?

Inner classes.

Static inner class singleton

/ * * *@Description TODO
 * @Author Sitr
 * @Date2021/1/1 declare *@Version1.0 * * /


public class LazyInnerClassSingleton {
    private LazyInnerClassSingleton(a){}

    public static final LazyInnerClassSingleton getInstance(a){
        return LazyHolder.lazy;
    }

    private static class LazyHolder{
        private static final LazyInnerClassSingleton lazy = newLazyInnerClassSingleton(); }}Copy the code

With static internal classes, Future does not use Synchronize, so performance is better. The logic in LayzHolder is not executed until it is called from the outside, cleverly taking advantage of the inner class. The use of JVM low-level execution logic perfectly avoids thread-safety issues. It’s the best way to write it.

Although thread-safety issues have been resolved at this point, the constructor, while private, may be used for reflection. Therefore, we can modify the private constructor to:

    private LazyInnerClassSingleton(a){
        if(LazyHolder.lazy ! =null) {throw new RuntimeException("Building multiple instances is not allowed"); }}Copy the code

This solves the problem of breaking singletons due to reflection.

However, there are still cases where the singleton pattern can be broken. Serialization. Suppose we now have a class that implements the serialization interface. SeriableSingleton.java

/ * * *@DescriptionImplement lazy singleton * of serialization interface@Author Sitr
 * @Date 2021/1/2 0:12
 * @Version1.0 * * /


public class SeriableSingleton implements Serializable {
    / * * *@Author Sitr
     * @Description* Serialization is to convert the state in memory to bytecode form * and convert it into an IO stream that is written to somewhere else (disk, network IO) to store the state in memory permanently. Deserialization, on the other hand, converts persistent bytecode content into an IO stream by reading it from the IO stream, which in turn converts the read content into Java objects and recreates objects in the process@Date 0:13 2021/1/2
     * @Param
     * @return* * /

    public final static SeriableSingleton INSTANCE = new SeriableSingleton();
    private SeriableSingleton(a){}

    public static SeriableSingleton getInstance(a){return INSTANCE;}

}
Copy the code

Used for testing of class: SeriableSingletonTest. Java

/ * * *@DescriptionTest serialization break singleton *@Author Sitr
 * @Date 2021/1/2 0:11
 * @Version1.0 * * /


public class SeriableSingletonTest {
    public static void main(String[] args) {
        SeriableSingleton s1;
        SeriableSingleton s2 = SeriableSingleton.getInstance();

        FileOutputStream fos = null;

        try {
            fos = new FileOutputStream("SeriableSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (SeriableSingleton)ois.readObject();
            ois.close();


            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s1 == s2);
        } catch(Exception e) { e.printStackTrace(); }}}Copy the code

The execution result is as follows:

com.edu.pattern.singleton.seriable.SeriableSingleton@5a10411
com.edu.pattern.singleton.seriable.SeriableSingleton@4eec7777
false
Copy the code

Obviously, the singleton pattern is broken at this point. So how do we solve this? We can override the readResolve method by adding the following code from Seriablesingleton.java


    private Object readResolve(a){
        return INSTANCE;
    }
Copy the code

Seriablesingleton.java now looks like this:


public class SeriableSingleton implements Serializable {

    public final static SeriableSingleton INSTANCE = new SeriableSingleton();
    private SeriableSingleton(a){}

    public static SeriableSingleton getInstance(a){returnINSTANCE; }private Object readResolve(a){
        returnINSTANCE; }}Copy the code

Can be solved.

So, why should readResolve() be overridden? We can look at the source code. In SeriableSingletonTest. In Java, we are through

            s1 = (SeriableSingleton)ois.readObject();
Copy the code

To the s1 object. It starts with readObject() and then strong through SeriableSingleton. Take a look at the readObject.

I’ve found that different versions of the JDK actually have different sources for serialization. Here I follow the version on my own computer

C:\Program Files\Java\jdk18.0._261\bin>java -version

java version"1.8.0 comes with_261"

Java(TM) SE Runtime Environment (build1.8.0 comes with_261-b12)

Java HotSpot(TM) 64 -Bit Server VM (build25.261 -b12.mixed mode)
Copy the code

We can see that the readObject class is implemented as follows:

    public final Object readObject(a)
        throws IOException, ClassNotFoundException {
        return readObject(Object.class);
    }

    /**
     * Reads a String and only a string.
     *
     * @return  the String read
     * @throws  EOFException If end of file is reached.
     * @throws  IOException If other I/O error has occurred.
     */
     
Copy the code

Further down, see readObject(Object.class)


private final Object readObject(Class
        type)
        throws IOException, ClassNotFoundException
    {
        if (enableOverride) {
            return readObjectOverride();
        }

        if (! (type == Object.class || type == String.class))
            throw new AssertionError("internal error");

        // if nested read, passHandle contains handle of enclosing object
        int outerHandle = passHandle;
        try {
            Object obj = readObject0(type, false);
            handles.markDependency(outerHandle, passHandle);
            ClassNotFoundException ex = handles.lookupException(passHandle);
            if(ex ! =null) {
                throw ex;
            }
            if (depth == 0) {
                vlist.doCallbacks();
            }
            return obj;
        } finally {
            passHandle = outerHandle;
            if (closed && depth == 0) { clear(); }}}Copy the code

Here, we can see that the returned Object.class comes from

Object obj = readObject0(type, false);
Copy the code

This line of code, readObject0. Let’s continue with the readObject0 internal source code. Let’s focus on the following paragraph.

case TC_OBJECT:
   if (type == String.class) {
       throw new ClassCastException("Cannot cast an object to java.lang.String");
   }
   return checkResolve(readOrdinaryObject(unshared));

Copy the code

There is a checkResolve query resolution that points to readOrdinaryObject to read binary objects. We continue to look inside the readOrdinaryObject. Then, we can find a paragraph in this method:

Object obj;
  try {
      obj = desc.isInstantiable() ? desc.newInstance() : null;
  } catch (Exception ex) {
      throw (IOException) new InvalidClassException(
          desc.forClass().getName(),
          "unable to create instance").initCause(ex);
  }
Copy the code

In this case, isInstantiable() is used to check whether the class can be initialized. If it can be initialized, build an object using newInstance(). If it cannot be initialized, null is returned. How is isInstantiable() checked?

boolean isInstantiable(a) {
    requireInitialized();
    return(cons ! =null);
}
Copy the code

The constructor is represented by cons. If the constructor is empty, false is returned and no initialization is done. Obviously, our previous SeriableSingleton has a constructor. (Although private, this is like farting with your pants down for the JVM.) So cons will not be empty, that is, will return a true to create an object that calls desc.newinstance ().

Therefore, the singleton pattern is broken.

So how to solve this problem? Going back to the readOrdinaryObject method, we can see that after executing newInstance, the following code continues:

if(obj ! =null &&
    handles.lookupException(passHandle) == null &&
    desc.hasReadResolveMethod())
{
    Object rep = desc.invokeReadResolve(obj);
    if (unshared && rep.getClass().isArray()) {
        rep = cloneArray(rep);
    }
    if(rep ! = obj) {// Filter the replacement object
        if(rep ! =null) {
            if (rep.getClass().isArray()) {
                filterCheck(rep.getClass(), Array.getLength(rep));
            } else {
                filterCheck(rep.getClass(), -1); } } handles.setObject(passHandle, obj = rep); }}Copy the code

HasReadResolveMethod () is called here to see if the ReadResolve method exists. If so, it will execute desc. InvokeReadResolve (obj). If not, the call is not made and the previously initialized code is used.

HasReadResolveMethod () ¶ hasReadResolveMethod() ¶ Let’s go to hasReadResolveMethod()

boolean hasReadResolveMethod(a) {
    requireInitialized();
    return(readResolveMethod ! =null);
}
Copy the code

Check whether readResolveMethod is null, and readResolveMethod comes from the same class:

private Method readResolveMethod;

/** local class descriptor for represented class (may point to self) */
Copy the code
readResolveMethod = getInheritableMethod(
    cl, "readResolve".null, Object.class);
Copy the code

Reflection is used to find if the readResolve method exists, which returns true. As long as readResolveMethod is true in this variable, the invokeReadResolve method is executed, using the value obtained by this method instead. The invokeReadResolve method:

Object invokeReadResolve(Object obj)
    throws IOException, UnsupportedOperationException
{
    requireInitialized();
    if(readResolveMethod ! =null) {
        try {
            return readResolveMethod.invoke(obj, (Object[]) null);
        } catch (InvocationTargetException ex) {
            Throwable th = ex.getTargetException();
            if (th instanceof ObjectStreamException) {
                throw (ObjectStreamException) th;
            } else {
                throwMiscException(th);
                throw new InternalError(th);  // never reached}}catch (IllegalAccessException ex) {
            // should not occur, as access checks have been suppressed
            throw newInternalError(ex); }}else {
        throw newUnsupportedOperationException(); }}Copy the code

It is then easy to understand that the implemented ReadResolve method is used within this method via invoke. That is:

return readResolveMethod.invoke(obj, (Object[]) null);
Copy the code

So, even if you override the ReadResolve method, you’re actually creating the class twice, but the later one overrides the previously created object. Objects previously deserialized are reclaimed by the GC.

How powerful you think you are, in fact, the JVM has already seen through everything.

The best singleton is actually a registered singleton.

Registered singleton

Registered singleton means that each instance is cached in a unified container and the instance is obtained using a unique identifier.

Enumerated singleton

/ * * *@DescriptionEnumerated singleton *@Author Sitr
 * @Date 2021/1/2 1:30
 * @Version1.0 * * /


public enum  EnumSingleton {
    INSTANCE;

    private Object data;

    public Object getData(a) {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    // Public access point
    public static EnumSingleton getInstance(a){
        returnINSTANCE; }}Copy the code

Enumerated singletons are a good solution to the problem of breaking singletons in deserialization. (In fact, enumeration singletons are compiled to hanhan-type singletons.) So again, why can enumeration singletons solve deserialization problems? Go ahead and click on that readObject and go to ObjectinputStream.java. We can continue with the objectinputStream.java class we just deserialized

case TC_ENUM:
    if (type == String.class) {
        throw new ClassCastException("Cannot cast an enum to java.lang.String");
    }
    return checkResolve(readEnum(unshared));
Copy the code

View the readEnum method. You can see that enumeration objects are determined by Enum. ValueOf.

if(cl ! =null) {
    try {
        @SuppressWarnings("unchecked")Enum<? > en = Enum.valueOf((Class)cl, name); result = en; }catch (IllegalArgumentException ex) {
        throw (IOException) new InvalidObjectException(
            "enum constant " + name + " does not exist in " +
            cl).initCause(ex);
    }
    if (!unshared) {
        handles.setObject(enumHandle, result);
    }
}
Copy the code

Notice that it is this row that is enumerated only once. This is because the enumeration generated here is determined by the class name and enumeration name. Enumeration values are registered with these two values. Since the value is deterministic and single, the generated object must also be singleton.

Enum<? > en = Enum.valueOf((Class)cl, name);Copy the code

But how on earth does it break a singleton through enumeration without deserialization?

Wake up and find it’s on the front page?

I’m just scribbling notes…

Let’s move on. This is because the JDK level ensures that we don’t create enumerations by reflection or serialization. Open the reflective type of Java. Lang. Reflect. Constructor. Java, inside newInstance method, have so a line:

if((clazz.getModifiers() & Modifier.ENUM) ! =0)
    throw new IllegalArgumentException("Cannot reflectively create enum objects");
Copy the code

If the Modifier enumeration is not 0, that is, an enumeration type, then throw an IllegalArgumentException(“Cannot reflectively create enum Objects “).

Container singleton

The singleton pattern adopted by Spring.

/ * * *@DescriptionContainer singleton *@Author Sitr
 * @Date2021/1/2 "*@Version1.0 * * /


public class ContainerSingleton {
    private ContainerSingleton(a){}

    private static Map<String,Object> ioc = new ConcurrentHashMap<>();

    public static Object getBean(String className){
        synchronized (ioc){
            if(! ioc.containsKey(className)){ Object obj =null;
                try{
                    obj = Class.forName(className).newInstance();// Use simple factory mode.
                    ioc.put(className,obj);
                }catch (Exception e){
                    e.printStackTrace();
                }
                return obj;
            }
            returnioc.get(className); }}}Copy the code

The biggest advantage of container singletons is that they are easy to manage and lazy to load. Therefore, if you do not add Synchronize, there may be a thread safety problem. Adding Synchronize solves the problem of thread safety but affects performance. Spring implements the singleton pattern in this way.

Singleton of ThreadLocal

Thread-safe singleton pattern within threads ensures global uniqueness within threads and is inherently thread-safe. But when you switch threads, it’s no longer thread-safe, it’s pseudo-thread-safe.

/ * * *@DescriptionSingleton pattern implemented by ThreadLocal *@Author Sitr
 * @Date2021/1/2 "*@Version1.0 * * /


public class ThreadLocalSingleton {

    private ThreadLocalSingleton(a){}

    private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = new ThreadLocal<ThreadLocalSingleton>(){
        @Override
        protected ThreadLocalSingleton initialValue(a) {
            return newThreadLocalSingleton(); }};public static ThreadLocalSingleton getInstance(a){
        returnthreadLocalInstance.get(); }}Copy the code

How does the ThreadLocal singleton ensure global uniqueness within a thread? We look at the Java. Lang. ThreadLocal. Java source code.

public T get(a) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if(map ! =null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if(e ! =null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            returnresult; }}return setInitialValue();
}

Copy the code

We can see that the GET here is obtained by ThreadLocalMap. Let’s continue looking at the GET part of ThreadLocalMap.

  public void set(T value) {
      Thread t = Thread.currentThread();
      ThreadLocalMap map = getMap(t);
      if(map ! =null)
          map.set(this, value);
      else
          createMap(t, value);
  }
Copy the code

As you can see, map.set is used to register singletons with this and value. So this is also a singleton of registered classes. This singleton pattern is often used in ORM frameworks to configure data sources and implement ThreadLocal to dynamically switch between multiple data sources.

Advantages of the singleton pattern

The singleton mode has only one instance in memory, which can effectively reduce memory overhead and avoid multiple resource occupation. At the same time set up the global access point, you can strictly control the access.

Disadvantages of the singleton pattern

The disadvantage is also obvious, not close the principle of open. Without interfaces, it would be difficult to extend. If you want to extend the singleton pattern, you can only do so by modifying the code.