The disadvantages of the general singleton writing method

This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

The general notation we see for singletons is generally the standard notation for hunchman singletons. The hanchian singleton is initialized as soon as the class is loaded and the singleton object is created. It is absolutely thread-safe, instantiated before the thread even exists, and there can be no access security issues. There is another way to write hunchman singletons, and the code is as follows.


// Static block singleton mode
public class HungryStaticSingleton {
    private static final HungryStaticSingleton instance;
    
    static {
        instance = new HungryStaticSingleton();
    }

    private HungryStaticSingleton(a){}

    public static HungryStaticSingleton getInstance(a){
        returninstance; }}Copy the code

This approach uses the static block mechanism, which is very simple and easy to understand. Hunhan-style singleton pattern is suitable for the case with fewer singleton objects. Writing in this way ensures absolute thread safety and high execution efficiency. However, the disadvantage is obvious, that is, all object classes are instantiated at load time. In this way, if a large number of singleton objects exist in the system, and the number of singleton objects is not determined, the system initialization will cause a large amount of memory waste, resulting in uncontrolled system memory. That is, objects, whether they are used or not, take up space, waste memory, and maybe take up memory but don’t use it. Is there a better way to write it? Let’s go ahead and analyze.

Restore the scene of the thread failure singleton

In order to solve the memory waste problem caused by hungry singletons, lazy singletons were developed. Lazy singleton writing is characterized by the fact that singleton objects are initialized only when they are used. A simple implementation of lazy singleton is as follows.


// Lazy singletons are instantiated only when needed externally
public class LazySimpleSingletion {
    // Static block, common memory area
    private static LazySimpleSingletion instance;

    private LazySimpleSingletion(a){}

    public  static LazySimpleSingletion getInstance(a){
        if(instance == null){
            instance = new LazySimpleSingletion();
        }
        returninstance; }}Copy the code

But writing this raises a new issue, which is thread safety in a multithreaded environment. To simulate this, write the thread class ExectorThread.

public class ExectorThread implements Runnable{
    @Override
    public void run(a) {
        LazySimpleSingleton singleton = LazySimpleSingleton.getInstance();
        System.out.println(Thread.currentThread().getName() + ":"+ singleton); }}Copy the code

Write the client test code as follows.

public class LazySimpleSingletonTest {
    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("End"); }}Copy the code

We run the code over and over again, and we find that there is a certain probability that two threads will get the same object, or two threads will get the same object. The following is the result of two threads getting inconsistent objects.

The following is the result of the same object acquired by the two threads.

Obviously, this means that the above singleton is a thread-safe issue. So how did this result come about? If both threads enter the getInstance() method at the same time, the if(null == instance) condition is met, creating two objects. If both threads continue to execute later code, it is possible that the result of the later thread overwrites the result of the first thread. If the print action occurs before the override, the final result is consistent; If the print action occurs after the override, you get two different results.

It is possible, of course, that no concurrency occurs and that it is perfectly normal. Let’s take a deeper look at debugging. Here we teach you a new skill, using thread mode debugging, manually control the thread execution order to track memory changes. Start by putting a breakpoint on the ExectorThread class, as shown below.

Right-click on the breakpoint and switch to Thread mode, as shown below.

Then set a breakpoint on the LazySimpleSingleton class, also marked as Thread mode, as shown in the figure below.

Switch back to the client test code, also make a breakpoint, and change to Thread mode, as shown below.

Once you start debugging, you can see that the Debug console is free to toggle the running state of Thread, as shown in the figure below.

By constantly switching threads and observing their memory state, we found that the LazySimpleSingleton was instantiated twice in a threaded environment. Sometimes the result may be two identical objects that are actually overwritten by subsequent threads of execution, which gives us an illusion that thread-safety risks still exist. So how do you optimize your code to make lazy singletons safe in a threaded environment? Take a look at the code below, adding the synchronized keyword to the getInstance() method to make it a thread-synchronized method.


public class LazySimpleSingletion {
    // Static block, common memory area
    private static LazySimpleSingletion instance;

    private LazySimpleSingletion(a){}

    public synchronized static LazySimpleSingletion getInstance(a){
        if(instance == null){
            instance = new LazySimpleSingletion();
        }
        returninstance; }}Copy the code

Let’s debug again. When one thread executes and calls the getInstance() method, the other thread calls the getInstance() method, and the state of the thread changes from RUNNING to MONITOR and blocks. Until the first thread completes, the second thread returns to the RUNNING state and continues calling the getInstance() method, as shown in the figure below.

In this way, the thread-safety issue is addressed through the use of synchronized.

3 double check lock singleton writing method shine

In the previous section, we did a good job of debugging synchronized monitor locks. However, if the number of threads increases dramatically, using synchronized locks will cause a large number of threads to block, resulting in a significant decline in program performance. It is just like the subway entering the station to limit the flow. In the bitter winter, all the people turn around in the square in front of the station, and the user experience is very bad, as shown in the picture below.

Is there a way to optimize the user experience? You can actually get everyone into the pit hall first, and then add some gates, so the user experience is better and the efficiency of the pit stops is improved. Of course, there are many hard and fast restrictions in real life, but in the virtual world, it is completely achievable. In fact, this is called double check, once at the entrance gate and again at the gate after entering the hall, as shown in the picture below.

Let’s modify the code to create the LazyDoubleCheckSingleton class.


public class LazyDoubleCheckSingleton {
    private volatile static LazyDoubleCheckSingleton instance;
    private LazyDoubleCheckSingleton(a){}

    public static LazyDoubleCheckSingleton getInstance(a){
        synchronized (LazyDoubleCheckSingleton.class) {
            if (instance == null) {
                instance = newLazyDoubleCheckSingleton(); }}returninstance; }}Copy the code

Does that solve the problem? And it turns out, visually, that this is exactly the same as LazySimpleSingletion, but it still blocks massively. What if we take the judgment up a notch?


public class LazyDoubleCheckSingleton {
    private volatile static LazyDoubleCheckSingleton instance;
    private LazyDoubleCheckSingleton(a){}

    public static LazyDoubleCheckSingleton getInstance(a){
        if (instance == null) {
              synchronized (LazyDoubleCheckSingleton.class) {
                   instance = newLazyDoubleCheckSingleton(); }}returninstance; }}Copy the code

After you run the code, there are still thread-safety issues. The running result is shown in the figure below.

What causes this? In fact, if both threads satisfy the if(instance == NULL) condition at the same time, both threads will execute the code in the synchronized block, and thus create it twice. Optimize the code again.


public class LazyDoubleCheckSingleton {
    private volatile static LazyDoubleCheckSingleton instance;
    private LazyDoubleCheckSingleton(a){}

    public static LazyDoubleCheckSingleton getInstance(a){
        // Check whether to block
        if (instance == null) {
            synchronized (LazyDoubleCheckSingleton.class) {
                // Check whether you want to recreate the instance
                if (instance == null) {
                    instance = new LazyDoubleCheckSingleton();
                    // Instruction reorder problem}}}returninstance; }}Copy the code

We debug breakpoints as shown in the figure below.

When the first thread calls the getInstance() method, the second thread can also call it. When the first thread executes to synchronized, it locks, and the second thread becomes MONITOR and blocks. At this point, the blocking is not based on the entire LazyDoubleCheckSingleton class, but inside the getInstance() method, as long as the logic is not too complicated to be felt by the caller.

A seemingly perfect static inner class singleton

Double-checked lock singletons solve thread safety and performance problems, but as long as the synchronized keyword is always locked, there is a certain impact on program performance. Is there really no better way? B: of course. One way to think about class initialization is to look at the following code, using static inner classes.


// This takes into account both the memory waste of hanky-hank singletons and the performance problems of synchronized
// Perfectly shielded from these two shortcomings
public class LazyStaticInnerClassSingleton {
    // When using LazyInnerClassGeneral, the inner class is initialized by default
    // If not used, the inner class is not loaded
    private LazyStaticInnerClassSingleton(a){}// Each keyword is not redundant. Static is used to share the space of the singleton, ensuring that the method cannot be overridden or overloaded
    private static LazyStaticInnerClassSingleton getInstance(a){
        // The inner class must be loaded before the result is returned
        return LazyHolder.INSTANCE;
    }

    // Internal classes are not loaded by default
    private static class LazyHolder{
        private static final LazyStaticInnerClassSingleton INSTANCE = newLazyStaticInnerClassSingleton(); }}Copy the code

This approach takes into account both the memory waste problem of hunger-singleton writing and the performance problem of synchronized. The inner class must be initialized before the method is called, neatly avoiding thread-safety issues. Because of the simplicity of this approach, step by step debugging is no longer necessary. However, “gold is not perfect, no one is perfect”, singleton mode is the same. Is this really perfect?

Restore the accident scene of reflection failure singleton

Let’s take a look at an accident site. Did you notice that the singleton constructor described above does nothing except add the private keyword. If its constructor is called using reflection and then the getInstance() method is called, there should be two different instances. Now the client test code, LazyStaticInnerClassSingleton, for example.


    public static void main(String[] args) {
        try{
            // If someone maliciously uses reflection to destroyClass<? > clazz = LazyStaticInnerClassSingleton.class;// Get the private constructor by reflection
            Constructor c = clazz.getDeclaredConstructor(null);
            // Force access
            c.setAccessible(true);

            // Violence initialization
            Object o1 = c.newInstance();
            
            // The constructor is called twice, which is equivalent to "new" twice
            Object o2 = c.newInstance();

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

The running result is shown in the figure below.

Obviously, two different instances are created in memory. So what to do? Let’s do an optimization. We put some restrictions in its constructor to throw an exception as soon as multiple iterations occur. The optimized code looks like this.


public class LazyStaticInnerClassSingleton {
    // When using LazyInnerClassGeneral, the inner class is initialized by default
    // If not used, the inner class is not loaded
    private LazyStaticInnerClassSingleton(a){
        if(LazyHolder.INSTANCE ! =null) {throw new RuntimeException("Creating multiple instances is not allowed"); }}// Each keyword is not redundant. Static is used to share the space of the singleton, ensuring that the method cannot be overridden or overloaded
    private static LazyStaticInnerClassSingleton getInstance(a){
        // The inner class must be loaded before the result is returned
        return LazyHolder.INSTANCE;
    }

    // Internal classes are not loaded by default
    private static class LazyHolder{
        private static final LazyStaticInnerClassSingleton INSTANCE = newLazyStaticInnerClassSingleton(); }}Copy the code

Run the client test code again, and the result is shown below.

At this point, you’re done writing what you consider the most elegant singleton pattern. However, the seemingly perfect singleton above is worth considering. Throwing an exception in a constructor is clearly not elegant. Is there a more elegant way to write a singleton than a static inner class?

More elegant enumeration singletons were created

Enumeration singletons can solve the above problems. Let’s start with the standard way of writing enumerated singletons, creating the EnumSingleton class.


public enum EnumSingleton {
    INSTANCE;
    private Object data;
    public Object getData(a) {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
    public static EnumSingleton getInstance(a){
        returnINSTANCE; }}Copy the code

Then look at the client test code.


public class EnumSingletonTest {
    public static void main(String[] args) {
        try {
            EnumSingleton instance1 = null;

            EnumSingleton instance2 = EnumSingleton.getInstance();
            instance2.setData(new Object());

            FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(instance2);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("EnumSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            instance1 = (EnumSingleton) ois.readObject();
            ois.close();

            System.out.println(instance1.getData());
            System.out.println(instance2.getData());
            System.out.println(instance1.getData() == instance2.getData());

        }catch(Exception e){ e.printStackTrace(); }}}Copy the code

Finally, the results are obtained, as shown in the figure below.

We didn’t do anything to the code logic, but it worked as expected. So what is the mystery of enumeration singletons? The following through the analysis of the source code to uncover its mystery. Download Jad, a very useful Java decompression tool, and configure the environment variables (not covered here) to use the command line. Go to the Class directory where the project is located and copy the path where Enumsingleton.class is located, as shown in the figure below.

Then switch to the command line, switch to the Class directory where the project is located, type jad and enter the copied path. In the Class directory, an enumletlet.jad file will appear. Opening the enumsingleton.jad file, we were surprised to find the following code.


static 
{
    INSTANCE = new EnumSingleton("INSTANCE".0);
    $VALUES = (new EnumSingleton[] {
        INSTANCE
    });
}

Copy the code

In fact, enumeration singletons assign INSTANCE values in static code blocks, which is an implementation of hanchian singletons. At this point, we can also consider whether serialization can break enumeration singletons. Take a look at the JDK source code again and go back to the readObject0() method of ObjectInputStream.


private Object readObject0(boolean unshared) throws IOException {...case TC_ENUM:
                returncheckResolve(readEnum(unshared)); . }Copy the code

We see that the readEnum() method is called in readObject0(). The code for the readEnum() method is as follows.


privateEnum<? > readEnum(boolean unshared) throws IOException {
    if(bin.readByte() ! = TC_ENUM) {throw new InternalError();
    }

    ObjectStreamClass desc = readClassDesc(false);
    if(! desc.isEnum()) {throw new InvalidClassException("non-enum class: " + desc);
    }

    int enumHandle = handles.assign(unshared ? unsharedMarker : null);
    ClassNotFoundException resolveEx = desc.getResolveException();
    if(resolveEx ! =null) {
        handles.markException(enumHandle, resolveEx);
    }

    String name = readString(false); Enum<? > result =null; Class<? > cl = desc.forClass();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); } } handles.finish(enumHandle); passHandle = enumHandle;return result;
}

Copy the code

As you can see above, enumeration types actually find a unique enumeration object by class name and class object. Therefore, enumeration objects cannot be loaded more than once by the class loader. Can reflection break enumerated singletons? Take a look at the client test code.


public static void main(String[] args) {
    try {
        Class clazz = EnumSingleton.class;
        Constructor c = clazz.getDeclaredConstructor();
        c.newInstance();
    }catch(Exception e){ e.printStackTrace(); }}Copy the code

The running result is shown in the figure below.

Results out of the center daily news is a Java. Lang. NoSuchMethodException abnormalities, mean did not find a no-parameter constructor. At this point, open the java.lang.Enum source code and look at its constructor. There is only one protected constructor.


protected Enum(String name, int ordinal) {
    this.name = name;
    this.ordinal = ordinal;
}

Copy the code

Let’s do another test like this.


public static void main(String[] args) {
    try {
        Class clazz = EnumSingleton.class;
        Constructor c = clazz.getDeclaredConstructor(String.class,int.class);
        c.setAccessible(true);
        EnumSingleton enumSingleton = (EnumSingleton)c.newInstance("Tom".Awesome!);

    }catch(Exception e){ e.printStackTrace(); }}Copy the code

The running result is shown in the figure below.

At this point, the error is obvious: “Cannot reflectively create enum objects”, that is, reflection Cannot be used to create enumerated types. As usual, we want to take a look at the JDK source and enter the Constructor newInstance() method.


public T newInstance(Object ... initargs)
    throws InstantiationException, IllegalAccessException,
           IllegalArgumentException, InvocationTargetException
{
    if(! override) {if(! Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<? > caller = Reflection.getCallerClass(); checkAccess(caller, clazz,null, modifiers); }}if((clazz.getModifiers() & Modifier.ENUM) ! =0)
        throw new IllegalArgumentException("Cannot reflectively create enum objects");
    ConstructorAccessor ca = constructorAccessor; 
    if (ca == null) {
        ca = acquireConstructorAccessor();
    }
    @SuppressWarnings("unchecked")
    T inst = (T) ca.newInstance(initargs);
    return inst;
}

Copy the code

As you can see from the above code, we enforce newInstance() to throw an exception if the Modifier is of type Modifier.ENUM. Isn’t this the same as static inner class singletons? Yes, but there is an unknown risk of writing logical processing in the constructor, and the JDK’s processing is the most official, authoritative, and stable. Therefore, enumerated singletons are one of the singletons recommended in Effective Java. Are we pretty clear at this point? The syntactic specificity and reflection of JDK enumerations also help make enumeration singletons a more elegant implementation.

Restore the scene of the deserialization failure singleton

Once a singleton object is created, it is sometimes necessary to serialize the object and write it to disk, then read the object from disk and deserialize it to memory for the next use. Deserialized objects are reallocated, or recreated. If the target object of serialization is a singleton, it defeats the purpose of the singleton pattern.


// Deserialization breaks the singleton pattern
public class SeriableSingleton implements Serializable {
    // Serialization is the conversion of state in memory to bytecode
    // This is converted to an I/O stream that can be written elsewhere (disk, network I/O)
    // The state in memory is kept forever

    // Deserialization converts persistent bytecode content into an I/O stream
    // Read through the I/O stream to convert the read into Java objects
    // Objects are recreated during conversion
    public  final static SeriableSingleton INSTANCE = new SeriableSingleton();
    private SeriableSingleton(a){}

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

Write client-side test code.


    public static void main(String[] args) {
        SeriableSingleton s1 = null;
        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 running result is shown in the figure below.

As you can see from the results, the deserialized object is inconsistent with the manually created object and is instantiated twice, violating the design intent of the singleton pattern. So how do you guarantee that you can implement the singleton pattern even in serialization? It’s as simple as adding the readResolve() method. The optimized code looks like this.


public class SeriableSingleton implements Serializable {

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

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

Take a look at the results, as shown below.

You must be thinking: what is the reason for this? Why do you write that? It looks amazing, but it’s also a little puzzling. Let’s take a look at the source implementation of the JDK to understand. Enter the ObjectInputStream class’s readObject() method as follows.


public final Object readObject(a)
    throws IOException, ClassNotFoundException
{
    if (enableOverride) {
        return readObjectOverride();
    }

    int outerHandle = passHandle;
    try {
        Object obj = readObject0(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

As you can see, the overridden readObject0() method is called in the readObject() method. Enter the readObject0() method with the following source code.


private Object readObject0(boolean unshared) throws IOException {...case TC_OBJECT:
                returncheckResolve(readOrdinaryObject(unshared)); . }Copy the code

TC_OBJECT calls ObjectInputStream’s readOrdinaryObject() method.


private Object readOrdinaryObject(boolean unshared)
    throws IOException
{
    if(bin.readByte() ! = TC_OBJECT) {throw new InternalError();
    }

    ObjectStreamClass desc = readClassDesc(false); desc.checkDeserialize(); Class<? > cl = desc.forClass();if (cl == String.class || cl == Class.class
            || cl == ObjectStreamClass.class) {
        throw new InvalidClassException("invalid class descriptor");
    }

    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); }...return obj;
}

Copy the code

We noticed that ObjectStreamClass’s isInstantiable() method was called, and the isInstantiable() method source is shown below.


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

Copy the code

The code above is simple enough to check if the constructor is empty. Returns true if the constructor is not empty. This means that as long as the parameterless constructor is instantiated. At this point, there’s no real reason why adding the readResolve() method can prevent the singleton pattern from being broken. Go back to the readOrdinaryObject() method of ObjectInputStream and continue with the source code.


private Object readOrdinaryObject(boolean unshared)
    throws IOException
{
    if(bin.readByte() ! = TC_OBJECT) {throw new InternalError();
    }

    ObjectStreamClass desc = readClassDesc(false); desc.checkDeserialize(); Class<? > cl = desc.forClass();if (cl == String.class || cl == Class.class
            || cl == ObjectStreamClass.class) {
        throw new InvalidClassException("invalid class descriptor");
    }

    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); }...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) {if(rep ! =null) {
                if (rep.getClass().isArray()) {
                    filterCheck(rep.getClass(), Array.getLength(rep));
                } else {
                    filterCheck(rep.getClass(), -1); } } handles.setObject(passHandle, obj = rep); }}return obj;
}

Copy the code

The hasReadResolveMethod() method is called after checking whether the no-argument constructor exists.


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

Copy the code

The logic of the code above is very simple: determine whether readResolveMethod is empty, and return true if it is not. So where is readResolveMethod assigned? ReadResolveMethod is assigned to the private ObjectStreamClass() method.


readResolveMethod = getInheritableMethod(
    cl, "readResolve".null, Object.class);
		
Copy the code

The logic above simply finds a readResolve() method with no arguments by reflection and saves it. Going back to the readOrdinaryObject() method of ObjectInputStream, if the readResolve() method exists, the invokeReadResolve() method is called as follows.


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 newInternalError(th); }}catch (IllegalAccessException ex) {
            throw newInternalError(ex); }}else {
        throw newUnsupportedOperationException(); }}Copy the code

As you can see, the readResolveMethod method is called with reflection in the invokeReadResolve() method. An analysis of the JDK source code shows that while adding an instance of the readResolve() method to return solves the problem of the broken singleton pattern, the singleton is actually instantiated twice, but the newly created object is not returned. If the act of creating objects happens more frequently, which means that the memory allocation overhead will also increase, is there really no way to solve the root of the problem? In fact, the enumeration singleton avoids this problem because it creates all the objects at class load time.

Restore the scene of the clone destruction singleton

Suppose you had a scenario where the target of a clone happened to be a singleton, would that break the singleton? Of course, we wouldn’t do that if we knew, but what if something happened? Let’s change the code.


@Data
public class ConcretePrototype implements Cloneable {

    private static  ConcretePrototype instance = new ConcretePrototype();

    private ConcretePrototype(a){}

    public static ConcretePrototype getInstance(a){
        return instance;
    }

    @Override
    public ConcretePrototype clone(a) {
        try {
            return (ConcretePrototype)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null; }}}Copy the code

We privatize the constructor and provide the getInstance() method. Write the client test code as follows.


    public static void main(String[] args) {
        // Create a prototype object
        ConcretePrototype prototype = ConcretePrototype.getInstance();

        // Copy the prototype object
        ConcretePrototype cloneType = prototype.clone();

        System.out.println("Prototype object and clone object comparison:" + (prototype == cloneType));

}

Copy the code

The running result is shown in the figure below.

From the results of the run, two different objects are indeed created. In fact, the solution to prevent cloning from destroying singletons is very simple. Either our singleton class does not implement the Cloneable interface, or we can rewrite the clone() method and return a singleton in the clone() method as follows.


    @Override
    public ConcretePrototype clone(a) {
        return instance;
}

Copy the code

Container singletons solve the problem of mass production singletons

Although enumerated singletons are more elegant, there are some problems. Because it initializes all objects in class memory at class load time, this is actually no different from hanhanian singleton writing and is not suitable for scenarios where a large number of singleton objects are created. Let’s look at another way of writing the registered singleton pattern, the ContainerSingleton, and create the ContainerSingleton class.


public class ContainerSingleton {
    private ContainerSingleton(a){}
    private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();
    public static Object getBean(String className){
        synchronized (ioc) {
            if(! ioc.containsKey(className)) { Object obj =null;
                try {
                    obj = Class.forName(className).newInstance();
                    ioc.put(className, obj);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return obj;
            } else {
                returnioc.get(className); }}}}Copy the code

Container singletons are easy to manage in scenarios where a large number of singletons are created, but they are not thread-safe. At this point, the registration singleton is introduced. Take a look at the source code of the container singleton in Spring.


public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
      implements AutowireCapableBeanFactory {
   /** Cache of unfinished FactoryBean instances: FactoryBean name --> BeanWrapper */
   private final Map<String, BeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>(16); . }Copy the code

From the above code, the container for storing a singleton is actually a Map.

9 Attached egg: ThreadLocal thread singleton

The thread singleton implements ThreadLocal. ThreadLocal cannot guarantee that the objects it creates are globally unique, but it can guarantee that they are unique within a single thread and thread-safe. Let’s look at the code.


public class ThreadLocalSingleton {
    private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
            new ThreadLocal<ThreadLocalSingleton>(){
                @Override
                protected ThreadLocalSingleton initialValue(a) {
                    return newThreadLocalSingleton(); }};private ThreadLocalSingleton(a){}

    public static ThreadLocalSingleton getInstance(a){
        returnthreadLocalInstance.get(); }}Copy the code

The client test code is as follows.


public static void main(String[] args) {

    System.out.println(ThreadLocalSingleton.getInstance());
    System.out.println(ThreadLocalSingleton.getInstance());
    System.out.println(ThreadLocalSingleton.getInstance());
    System.out.println(ThreadLocalSingleton.getInstance());
    System.out.println(ThreadLocalSingleton.getInstance());

    Thread t1 = new Thread(new ExectorThread());
    Thread t2 = new Thread(new ExectorThread());
    t1.start();
    t2.start();
    System.out.println("End");
}

Copy the code

The running result is shown in the figure below.

As you can see from the above, no matter how many times the main thread is called, the same instance is obtained, and different instances are obtained in the two child threads. So how does ThreadLocal achieve this effect? We know that singletons lock methods for thread-safe purposes and trade time for space. ThreadLocal puts all objects in a ThreadLocalMap, providing one object for each thread, effectively implementing thread isolation in terms of space for time.

Pay attention to “Tom play architecture” reply to “design pattern” can obtain the complete source code.

Tom play architecture: 30 real cases of design patterns (attached source code), the challenge of annual salary 60W is not a dream

This article is “Tom play structure” original, reproduced please indicate the source. Technology is to share, I share my happiness! If this article is helpful to you, welcome to follow and like; If you have any suggestions can also leave a comment or private letter, your support is my motivation to adhere to the creation. Pay attention to “Tom bomb architecture” for more technical dry goods!