Categories
Uncategorized

Design pattern – singleton thread debugging and destroy as much as singleton

Foreword

Whether as a singleton (Detailed) and see if you understand – before the design pattern? One article, we mentioned the multi-threaded debugging tool developed by Idea, violent destruction of issues Singleton pattern; lack of space, now open a separate article presentation: thread safe singleton in a multithreaded why the situation is create multiple, how to destroy a single case.

If you do not know how to use the IDEA tool for debugging thread pattern hair before I read the article: you do not know IDEA Debug debugging tips

First, the thread-safe singleton in a multithreaded why the situation is to create multiple

Recalling the lazy man’s first single example of the code and test program code simple thread-safe:

/**
 * @author eamon.zhang
 * @date 2019-09-30 上午10:55
 */
public class LazySimpleSingleton {
    private LazySimpleSingleton(){}
    private static LazySimpleSingleton instance = null;

    public static LazySimpleSingleton getInstance(){
        if (instance == null) {
            instance = new LazySimpleSingleton();
        }
        return instance;
    }
}

// 测试程序
@Test
public void test() {
    try {
        ConcurrentExecutor.execute(() -> {
            LazySimpleSingleton instance = LazySimpleSingleton.getInstance();
            System.out.println(Thread.currentThread().getName() + " : " + instance);
        }, 2, 2);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

For this single case, we have no doubt that it is not thread-safe, as to why, then use the IDEA tool thread debug mode intuitively find out.

Break point on the critical code

    LazySimpleSingleton of singleton class if (instance == null) at:

    Test class, multi-threaded inlet call getInstance () at:

Start Debugging

    Start debug, we may find that we start a thread in the debug window:

    The pool-1-thread-1 step into thread if (instance == null) breakpoint observed instance is null;

    The execution pool-1-thread-1 to instance = new LazySimpleSingleton (); waiting at initialization:

    Switching threads pool-1-thread-2 to the same single-step if (instance == null) at the breakpoint, the value observed at this time instance can be null (the what we often say that the code is broken into two execution threads at the same time) :

    The same pool-1-thread-2 to perform instance = new LazySimpleSingleton (); waiting at initialization:

    Obviously, these two conditions are met threads if (instance == null), should be instantiated to perform operation corresponding to the code block, then it would be two threads are initialized:

Thread pool-1-thread-1 of the Example:

Switching threads pool-1-thread-2 has been observed value initialized instance, however, the thread pool-1-thread-2 will still be instantiated again:

Thread pool-1-thread-2 of the Example:

Are we clear out?

    The two threads executed, see the console:

As you can see, the object although the printed output is the same, however, is indeed created twice, but pool-1-thread-2 will be instantiated pool-1-thread-1 instantiated object value to cover a.

When I thread pool-1-thread-1 and thread pool-1-thread-2 while performing the instance = new LazySimpleSingleton (); then let execute post-pool-1-thread-1 after printing, and then pool- 1-thread-2 operation execution instance, you will see a print target would be not the same as a:

This is simulated by the order of execution thread debugging mode manually restore the thread of control multi-threaded environment, thread-unsafe situation.


Second, improved thread-safe singleton

We understand the reasons for not thread-safe is to get the two threads of instance resources are null, and thus are instantiated. Is there any way to solve it? Of course, to getInstance () plus the synchronized keyword, so that this method becomes a thread synchronization methods:

public class LazySimpleSingleton {
    private LazySimpleSingleton(){}
    private static LazySimpleSingleton instance = null;

    public synchronized static LazySimpleSingleton getInstance(){
        if (instance == null) {
            instance = new LazySimpleSingleton();
        }
        return instance;
    }
}

When we execute one thread and calls the getInstance () method, another thread calls the getInstance () method, the thread by RUNNING state into a MONITOR, a blockage. Until the first thread executing the second thread was restored RUNNING state continue to call getInstance () method

This solves said before thread-safety issues, but the number of threads like this in more cases, if the CPU allocation increased pressure will result in large quantities of thread jam, leading to a substantial decline in operating performance programs; and in order to solve thread safety performance issues, and thus we have a single case of double-checking style. There is not more to say.


Third, the destruction singleton

Under normal circumstances, we create a lazy single example uses the example of a hungry man single or double checking is no problem, but under certain circumstances, occur singleton is destroyed.

Damage for singleton

Under actual conditions, a company programmer to write a single case, but another programmer, may be more cattle X, a little different style of writing code, he came to call someone else to write the interface through reflection, it appears this is not a single case case of a single embodiment. This undermines singleton.

Show

When we write a single case, we have not noticed in front of the private constructor method modifiers only private, if we use reflection to invoke its constructor, and then call the getInstance () method, there will be two different instances.

We said earlier article singleton in LazyInnerClassSingleton for example, write test code to call reflection:

@Test
public void testReflex() {
    try {
        // 很无聊的情况下,进行破坏
        Class clazz = LazyInnerClassSingleton.class;
        // 通过反射拿到私有的构造方法
        Constructor c = clazz.getDeclaredConstructor(null);
        // 设置访问属性,强制访问
        c.setAccessible(true);

        // 暴力初始化两次,这就相当于调用了两次构造方法
        LazyInnerClassSingleton o1 = c.newInstance();
        LazyInnerClassSingleton o2 = c.newInstance();
        // 只要 o1和o2 地址不相等,就可以说明这是两个不同的对象,也就是违背了单例模式的初衷
        System.out.println(o1 == o2);
    } catch (Exception e) {
        e.printStackTrace();
    }

}

Results are as follows:

Obviously, that creates two different instances. Now, we do some limitations in its constructor, create repeated once appeared, directly throw an exception. After the point of view optimized code:

public class LazyInnerClassSingleton {

    private LazyInnerClassSingleton() {
        if(LazyHolder.INSTANCE != null){
            throw new RuntimeException("不允许创建多个实例");
        }
    }

    // 注意关键字final,保证方法不被重写和重载
    public static final LazyInnerClassSingleton getInstance() {
        return LazyHolder.INSTANCE;
    }

    private static class LazyHolder {
        // 注意 final 关键字(保证不被修改)
        private static final LazyInnerClassSingleton INSTANCE = new LazyInnerClassSingleton();
    }
}

Called again:

Thus, the embodiment avoids the problem of a single reflected destroyed.

Serialization destruction singleton

Another situation you may encounter, we need to serialize an object to disk, and then come back from the disk deserialize next time you use deserialized objects will be re-allocated memory, if it is a single serialized objects embodiment, it defeats the purpose of the single-mode embodiment. This is equivalent to the singleton destroyed.

Show

We were still LazyInnerClassSingleton, for example, will LazyInnerClassSingleton implement the Serializable interface;

Then write test code:

/**
 * @author eamon.zhang
 * @date 2019-10-08 下午3:06
 */
public class SerializableTest {
    public static void main(String[] args) {
        LazyInnerClassSingleton s1 = null;
        LazyInnerClassSingleton s2 = LazyInnerClassSingleton.getInstance();

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

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

            System.out.println(s1);
            System.out.println(s2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Execute the test code:

You can see, the result is two different objects. This defeats the purpose of the same single-mode embodiment. So how do we ensure that the situation serialization can achieve a single case of it? In fact, very simple to use readResolve () method can be:

public class LazyInnerClassSingleton implements Serializable {

    private LazyInnerClassSingleton() {
        if (LazyHolder.INSTANCE != null) {
            throw new RuntimeException("不允许创建多个实例");
        }
    }

    // 注意关键字final,保证方法不被重写和重载
    public static final LazyInnerClassSingleton getInstance() {
        return LazyHolder.INSTANCE;
    }

    private static class LazyHolder {
        // 注意 final 关键字(保证不被修改)
        private static final LazyInnerClassSingleton INSTANCE = new LazyInnerClassSingleton();
    }

    // 解决反序列化对象不一致问题
    private Object readResolve() {
        return LazyHolder.INSTANCE;
    }
}

I am sure you will ask, why?

To find out, we look at the JDK source code, we enter readObject ObjectInputStream class () method:

public final Object readObject() throws IOException, ClassNotFoundException {
        if (this.enableOverride) {
            return this.readObjectOverride();
        } else {
            int outerHandle = this.passHandle;

            Object var4;
            try {
                Object obj = this.readObject0(false);
                this.handles.markDependency(outerHandle, this.passHandle);
                ClassNotFoundException ex = this.handles.lookupException(this.passHandle);
                if (ex != null) {
                    throw ex;
                }

                if (this.depth == 0L) {
                    this.vlist.doCallbacks();
                    this.freeze();
                }

                var4 = obj;
            } finally {
                this.passHandle = outerHandle;
                if (this.closed && this.depth == 0L) {
                    this.clear();
                }

            }

            return var4;
        }
    }

We found that: readObject in turn calls us to rewrite the readObject0 () method, enter readObject0 () method:

private Object readObject0(boolean unshared) throws IOException {
        ...
        try {
            switch(tc) {
            ...
            case 115:
                var4 = this.checkResolve(this.readOrdinaryObject(unshared));
                return var4;
            ...
        } finally {
            --this.depth;
            this.bin.setBlockDataMode(oldMode);
        }

        return var4;
    }

We see the code calls the ObjectInputStream of readOrdinaryObject () method, we continue to look into the source code:

private Object readOrdinaryObject(boolean unshared) throws IOException {
        ...
            if (cl != String.class && cl != Class.class && cl != ObjectStreamClass.class) {
                Object obj;
                try {
                    obj = desc.isInstantiable() ? desc.newInstance() : null;
                } catch (Exception var7) {
                    throw (IOException)(new InvalidClassException(desc.forClass().getName(), "unable to create instance")).initCause(var7);
                }

        ...

        }
    }

Found isInstantiable ObjectStreamClass call () method, while isInstantiable () inside the code as follows:

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

The code is very simple, is to determine what the constructor is empty, not empty constructor returns true, that is, as long as the no-argument constructor will instantiate is; this time, in fact, have not found Why add readResolve () the method avoids the real reason singleton is destroyed, we once again return to the ObjectInputStream readOrdinaryObject () method continues to look down to find the following code:

private Object readOrdinaryObject(boolean unshared) throws IOException {
    ...
    if (obj != null && this.handles.lookupException(this.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()) {
                    this.filterCheck(rep.getClass(), Array.getLength(rep));
                } else {
                    this.filterCheck(rep.getClass(), -1);
                }
            }

            obj = rep;
            this.handles.setObject(this.passHandle, rep);
        }
    }
    ...
}

After the constructor with no arguments to determine whether there is, in turn, calls the hasReadResolveMethod () method:

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

Logic is very simple, is to determine whether readResolveMethod is empty, returns true not empty. ? So readResolveMethod where the assignment is to find it through the global assignment of code to find () method will be assigned to readResolveMethod in private methods ObjectStreamClass, look at the code:

 ObjectStreamClass.this.readResolveMethod = ObjectStreamClass.getInheritableMethod(cl, "readResolve", (Class[])null, Object.class);

Logic code is actually through reflection to find a no-argument readResolve () method, and preserved, and now back to the ObjectInputStream readOrdinaryObject () method continues to look down, if readResolve () exists is called invokeReadResolve () method:

Object invokeReadResolve(Object obj) throws IOException, UnsupportedOperationException {
    this.requireInitialized();
    if (this.readResolveMethod != null) {
        try {
            return this.readResolveMethod.invoke(obj, (Object[])null);
        } catch (InvocationTargetException var4) {
            Throwable th = var4.getTargetException();
            if (th instanceof ObjectStreamException) {
                throw (ObjectStreamException)th;
            } else {
                throwMiscException(th);
                throw new InternalError(th);
            }
        } catch (IllegalAccessException var5) {
            throw new InternalError(var5);
        }
    } else {
        throw new UnsupportedOperationException();
    }
}

We can see with invokeReadResolve () method is called reflection readResolveMethod () method. JDK through source code analysis we can see that, although the increase readResolve () method returns an instance, to solve the problem of the destruction of single embodiment. However, we are analyzing the source code and debugging, we can see that actually instantiated twice, but the newly created objects are not returned it.

What if, to create objects of action occurs frequency increases, it means that the memory allocation overhead increases along with it; To solve this problem, we recommend using a single registered cases.

Why are recommended (enumeration type) registered a single case of formula

We said in the previous article, we strongly recommend the use of a single case of an enumerated type; then we analyze the reasons:

Use Java decompiler Jad (to download), unzip, use the command line call:

./jad ~/IdeaProjects/own/java-advanced/01.DesignPatterns/design-patterns/build/classes/java/main/com/eamon/javadesignpatterns/singleton/enums/EnumSingleton.class

EnumSingleton.jad will generate a file in the current directory, we use vscode open this file to view:

public final class EnumSingleton extends Enum
{

    public static EnumSingleton[] values()
    {
        return (EnumSingleton[])$VALUES.clone();
    }

    public static EnumSingleton valueOf(String name)
    {
        return (EnumSingleton)Enum.valueOf(com/eamon/javadesignpatterns/singleton/enums/EnumSingleton, name);
    }

    private EnumSingleton(String s, int i)
    {
        super(s, i);
        instance = new EnumResource();
    }

    public Object getInstance()
    {
        return instance;
    }

    public static final EnumSingleton INSTANCE;
    private Object instance;
    private static final EnumSingleton $VALUES[];

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

Please note this code:

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

Example enumeration class original single static code block gave INSTANCE assigned a value, a formula implementations starving single embodiment. Then the same, we can sabotage it through reflection and serialization?

Sequence analysis of the way first:

Let’s get back JDK Source: () method has the following code of ObjectInputStream readObject0:

 private Object readObject0(boolean unshared) throws IOException {
    ...
        case 126:
            var4 = this.checkResolve(this.readEnum(unshared));
    ...

    return var4;
}

We see readObject0 call readEnum () () method, follow this method:

private Enum readEnum(boolean unshared) throws IOException {
    if (this.bin.readByte() != 126) {
        throw new InternalError();
    } else {
        ObjectStreamClass desc = this.readClassDesc(false);
        if (!desc.isEnum()) {
            throw new InvalidClassException("non-enum class: " + desc);
        } else {
            int enumHandle = this.handles.assign(unshared ? unsharedMarker : null);
            ClassNotFoundException resolveEx = desc.getResolveException();
            if (resolveEx != null) {
                this.handles.markException(enumHandle, resolveEx);
            }

            String name = this.readString(false);
            Enum result = null;
            Class cl = desc.forClass();
            if (cl != null) {
                try {
                    Enum en = Enum.valueOf(cl, name);
                    result = en;
                } catch (IllegalArgumentException var9) {
                    throw (IOException)(new InvalidObjectException("enum constant " + name + " does not exist in " + cl)).initCause(var9);
                }

                if (!unshared) {
                    this.handles.setObject(enumHandle, result);
                }
            }

            this.handles.finish(enumHandle);
            this.passHandle = enumHandle;
            return result;
        }
    }
}

In fact, we find enumerated type find a unique enumeration object by class name and the Class object classes. Therefore, the enumerable object can not be loaded multiple class loader.

Is it possible to sabotage it by reflex? Let’s take the following reflection damage enumeration class test code:

@Test
public void testEnum(){
    try {
        // 很无聊的情况下,进行破坏
        Class clazz = EnumSingleton.class;
        // 通过反射拿到私有的构造方法
        Constructor c = clazz.getDeclaredConstructor(null);
        // 设置访问属性,强制访问
        c.setAccessible(true);

        // 暴力初始化两次,这就相当于调用了两次构造方法
        EnumSingleton o1 = c.newInstance();
        EnumSingleton o2 = c.newInstance();
        // 只要 o1和o2 地址不相等,就可以说明这是两个不同的对象,也就是违背了单例模式的初衷
        System.out.println(o1 == o2);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Results of the:

Reported that the java.lang.NoSuchMethodException abnormal, meaning not find a constructor with no arguments.

Then we look at java.lang.Enum source, we find that it is only a protected constructor of:

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

Then we do a test like this:

@Test
public void testEnum1() {
    try {
        Class clazz = EnumSingleton.class;
        Constructor c = clazz.getDeclaredConstructor(String.class, int.class);
        c.setAccessible(true);
        EnumSingleton enumSingleton = (EnumSingleton) c.newInstance("Eamon", 666);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Error found in the console output is as follows:

Meaning that they can not create enumerated types with reflection. As for why, we look at JDK source code, enter the Constructor’s newInstance () method:

    public T newInstance(Object... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        if (!this.override) {
            Class caller = Reflection.getCallerClass();
            this.checkAccess(caller, this.clazz, this.clazz, this.modifiers);
        }

        if ((this.clazz.getModifiers() & 16384) != 0) {
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        } else {
            ConstructorAccessor ca = this.constructorAccessor;
            if (ca == null) {
                ca = this.acquireConstructorAccessor();
            }

            T inst = ca.newInstance(initargs);
            return inst;
        }
    }

It turned out that in the source code of enumeration types are mandatory judge (16384 represents the enumerated types), if it is enumerated types, direct throw an exception. So far it explains why “Effective Java” recommended enumerate the reasons to implement a single example: JDK enumeration grammar particularity, and also as a reflection escort enumeration, enumeration type to let a single case has become a more elegant implementation.


Source involved herein can be found on github, the relevant test code in the test package: https: //github.com/eamonzzz/java-advanced

Leave a Reply