Follow the public account “Java backend technology full Stack” reply “000” to obtain a large number of e-books

The story

To be honest, there are hundreds of versions of the singleton pattern online. You’ve probably seen many versions of it. But what does it matter? A buddy in my tech group interviewed last week for a singleton and sent him back to wait for notice.

Here’s the question the student was asked:

  • What are the characteristics of singletons?

  • Do you know the specific usage scenarios of singleton pattern?

  • How many ways are singletons commonly written?

  • How to ensure thread safety?

  • How can it not be hit by reflex?

  • How to guarantee against serialization and deserialization attacks?

  • Will enumerations be serialized?

.

You can also try answering these questions and see how many you can answer.

define

The Singleton Pattern is one of the simplest design patterns in Java. This type of design pattern is the creation pattern, which provides the best way to create objects.

This pattern involves a single class that is responsible for creating its own objects while ensuring that only a single object is created. This class provides a way to access its unique objects directly, without instantiating the objects of the class.

Features:

  • 1. A singleton class can have only one instance.

  • 2. A singleton class must create its own unique instance.

  • 3. The singleton class must provide this instance to all other objects

  • 4. Hide all construction methods

** Purpose: ** Ensures that a class has only one instance and provides a global access point to access it.

Case study: A company can only have one CEO, and having more than one CEO is a mess.

Usage scenarios

You need to ensure that there is absolutely only one instance in any case.

Examples include ServletContext, ServletConfig, ApplicationContext, DBTool, and so on.

Writing the singleton pattern

  • The hungry type

  • Lazy (includes double-checked locks, static inner classes)

  • Registration (using enumeration as an example)

The hungry type

As the name suggests, hungry people: Hungry people need to eat first, so, it’s done from the start.

Static, static, static, static, static, static, static

public class HungrySingleton{ private static final HungrySingleton INSTANCE; static { INSTANCE=new HungrySingleton(); } // private static final HungrySingleton INSTANCE=new HungrySingleton(); private HungrySingleton(){ } public static HungrySingleton getInstance(){ return INSTANCE; }}Copy the code

The hungry style has a fatal drawback: it wastes space and does not need to be instantiated. Imagine the horror of thousands of them doing the same.

And then, you know, can you instantiate it when you use it, and that leads to slacker style.

LanHanShi

As the name implies, it is needed to create, because lazy, you do not call my method, I will not work.

Here is a lazy Java code implementation:

public class LazySingleton {

    private static LazySingleton lazySingleton = null;

    private LazySingleton(a) {}public static LazySingleton getInstance(a) {
        if (lazySingleton == null) {/ / 01
            lazySingleton = new LazySingleton();/ /.
        }
        returnlazySingleton; }}Copy the code

Enter the getInstance method, determine if lazySingleton is empty, create an object if it is, and return it.

But here’s the thing:

Both threads enter the getInstance method at the same time, each executes line 01, both true, each creates an object, and then returns the object they created.

Isn’t it not satisfying to have only one object? So there are thread-safety issues, so how do you solve them?

The first thing I think of is locking. Hence the thread-safe lazy loading version:

public class LazySingleton {

    private static LazySingleton lazySingleton = null;

    private LazySingleton(a) {}// A simple and crude solution to thread-safety problems
    // Performance issues still exist
  public synchronized static LazySingleton getInstance(a) {
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        returnlazySingleton; }}Copy the code

The getInstance method is marked with synchronized, but it also involves the problem of lock. Synchronization lock is a good influence on system performance. Although it has been optimized after JDK1.6, it still involves the cost of lock.

Every time a thread calls the getInstance method, a lock is involved, so this has been optimized to become the familiar double-checked lock.

Double check lock

The code implementation is as follows:


public class LazyDoubleCheckSingleton { 
    private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;

    private LazyDoubleCheckSingleton(a) {}public static LazyDoubleCheckSingleton getInstance(a) {
        if (lazyDoubleCheckSingleton == null) {/ / 01
            synchronized (LazyDoubleCheckSingleton.class) {
                if (lazyDoubleCheckSingleton == null) {/ /.
                    lazyDoubleCheckSingleton = newLazyDoubleCheckSingleton(); }}}returnlazyDoubleCheckSingleton; }}Copy the code

In this code, at line 01, if it’s not empty, it just returns, so that’s the first check. If it is empty, the synchronization block is entered, and line 02 is checked again.

Double checking is the real if judgment, the acquisition of class object lock, if judgment.

The above code appears to have some problems, such as instruction reorder (requires JVM knowledge).

What does reordering mean?

Like a simple sentence in Java

lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();

Copy the code

The JVM instructions are compiled by the compiler as follows:

memory =allocate(); //1: allocates memory space for the object

ctorInstance(memory); //2: initializes the object

instance =memory; //3: Sets instance to the newly allocated memory address

However, the order of these instructions is not fixed, and may be optimized by the JVM and CPU to rearrange the instructions in the following order:

memory =allocate(); //1: allocates memory space for the object

instance =memory; //3: Sets instance to the newly allocated memory address

ctorInstance(memory); //2: initializes the object

To prevent instruction reordering, we can use volatile (note that volatile prevents instruction reordering and thread visibility).

So, a better version came out.


public class LazyDoubleCheckSingleton {
    // Use volatile modifier
    private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null; 
    private LazyDoubleCheckSingleton(a) {}public static LazyDoubleCheckSingleton getInstance(a) {
        if (lazyDoubleCheckSingleton == null) {
            synchronized (LazyDoubleCheckSingleton.class) {
                if (lazyDoubleCheckSingleton == null) {
                    lazyDoubleCheckSingleton = newLazyDoubleCheckSingleton(); }}}returnlazyDoubleCheckSingleton; }}Copy the code

Although it is a significant improvement over the previous version, there are still synchronization locks, which can still affect performance. Therefore, it is optimized to static inner class mode:

Static inner class

Here is the code implementation of the static inner class:

public class LazyStaticSingleton {


    private LazyStaticSingleton(a) {}public static LazyStaticSingleton getInstance(a) {
        return LazyHolder.LAZY_STATIC_SINGLETON;
    }

    // Wait until the external method call is executed
    // Make use of inner class features
    //JVM low-level execution perfectly circumvented thread-safety issues
    private static class LazyHolder {
        private static final LazyStaticSingleton LAZY_STATIC_SINGLETON = newLazyStaticSingleton(); }}Copy the code

The use of internal classes is a perfect way to circumvent thread-safety issues in the UNDERLYING JVM, which is the preferred approach in many projects today.

But there are potential risks. What are the risks?

It is possible to use string modification with reflective violence, as well as create multiple instances:

The reflection code is implemented as follows:

import java.lang.reflect.Constructor;

public class LazyStaticSingletonTest {
    public static void main(String[] args) {
        try{ Class<? > clazz = LazyStaticSingleton.class; Constructor constructor = clazz.getDeclaredConstructor(null);
            // Forcible access
            constructor.setAccessible(true);
            Object object = constructor.newInstance();

            Object object1 = LazyStaticSingleton.getInstance();

            System.out.println(object == object1);
        } catch(Exception ex) { ex.printStackTrace(); }}}Copy the code

This code runs as false.

So, the above double-checked lock approach, via reflection, still has potential risks. What to do?

In The book Effect Java, the author recommends using enumerations to implement the singleton pattern because they cannot be reflected.

The enumeration

Here is the code implementation of the enumerated singleton pattern:

public enum EnumSingleton {
    INSTANCE;
    private Object data;

    public Object getData(a) {
        return data;
    }

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

We use the code reflected above to test the enumerated singleton pattern.

public class EnumTest {
    public static void main(String[] args) {
        try{ Class<? > clazz = EnumSingleton.class; Constructor constructor = clazz.getDeclaredConstructor(null);
            // Forcible access
            constructor.setAccessible(true);
            Object object = constructor.newInstance();

            Object object1 = EnumSingleton.getInstance();

            System.out.println(object == object1);
        } catch(Exception ex) { ex.printStackTrace(); }}}Copy the code

Run this code:

java.lang.NoSuchMethodException: com.tian.my_code.test.designpattern.singleton.EnumSingleton.<init>()
 at java.lang.Class.getConstructor0(Class.java:3082)
 at java.lang.Class.getDeclaredConstructor(Class.java:2178)
 at com.tian.my_code.test.designpattern.singleton.EnumTest.main(EnumTest.java:41)

Copy the code

You really can’t do it with reflection. If the interviewer is at this point, why can’t enumerations be reflected?

Why can’t enumerations be reflected?

We’re in reflection code

  Constructor constructor = clazz.getDeclaredConstructor(null);

Copy the code

This line of code gets its no-argument constructor. Also, from the error log, we can see that the error occurred in the getConstructor0 method, and that the no-argument constructor was not found.

Oddly enough, enumerations are classes, so if we don’t define a constructor for a class display, we’ll be given a constructor with no arguments by default.

So I came up with an idea: we could decompile the.class file of our enumeration singleton using jad.

Find the directory where our class file is located, and then we can execute the following command:

C:\Users\Administrator>jad D:\workspace\my_code\other-local-demo\target\classes
com\tian\my_code\test\designpattern\singleton\EnumSingleton.class
Parsing D:\workspace\my_code\other-local-demo\target\classes\com\tian\my_code\t
st\designpattern\singleton\EnumSingleton.class... Generating EnumSingleton.jad

Copy the code

Note: the class file directory and the directory where the jad file is generated.

Then open the enumsingleton.jad file:

So, I thought, let’s use the parameter constructor to create:

public class EnumTest {
    public static void main(String[] args) {
        try{ Class<? > clazz = EnumSingleton.class; Constructor constructor = clazz.getDeclaredConstructor(String.class,int.class);
            // Forcible access
            constructor.setAccessible(true);
            Object object = constructor.newInstance("Tian Weichang".996);

            Object object1 = EnumSingleton.getInstance();

            System.out.println(object == object1);
        } catch(Exception ex) { ex.printStackTrace(); }}}Copy the code

Running the code again results in:

java.lang.IllegalArgumentException: Cannot reflectively create enum objects
 at java.lang.reflect.Constructor.newInstance(Constructor.java: 417).at com.tian.my_code.test.designpattern.singleton.EnumTest.main(EnumTest.java45) :Copy the code

The hint is obvious: don’t use reflection to create enumerated objects.

    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); }}//Modifier.ENUM is used to check whether the Modifier is enumerated
        if((clazz.getModifiers() & Modifier.ENUM) ! =0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

Copy the code

So, here, we are really clear, why enumeration does not let reflection reasons.

Serialization destruction

Let’s demonstrate this in a non-thread-safe hanky-hank fashion to see how serialization breaks the pattern.

public class ReflectTest {

    public static void main(String[] args) {
        Singleton1 receives deserialized instances from the input stream
        HungrySingleton singleton1 = null;
        HungrySingleton singleton2 = HungrySingleton.getInstance();
        try {
            / / the serialization
            FileOutputStream fos = new FileOutputStream("HungrySingleton.txt");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(singleton2);
            oos.flush();
            oos.close();

            // deserialize
            FileInputStream fis = new FileInputStream("HungrySingleton.txt");
            ObjectInputStream ois = new ObjectInputStream(fis);
            singleton1 = (HungrySingleton) ois.readObject();
            ois.close();

            System.out.println(singleton1);
            System.out.println(singleton2);
            
            System.out.println(singleton1 == singleton2);

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

Running results:

com.tian.my_code.test.designpattern.singleton.HungrySingleton@7e6cbb7a
com.tian.my_code.test.designpattern.singleton.HungrySingleton@452b3a41
false

Copy the code

See?

Using serialization can break patterns in ways that may not be clear to many people.

How to prevent it?

We make a slight change to the non-thread-safe hanky code:

public class HungrySingleton implements Serializable{

    private static final HungrySingleton INSTANCE;
    static {
        INSTANCE=new HungrySingleton();
    } 
    private HungrySingleton(a){}public static HungrySingleton getInstance(a){
        return INSTANCE;
    }
    // Add the readResolve method and return INSTANCE
    privateObject readResolve method, and returns (){returnINSTANCE; }}Copy the code

Running the serialization test again gives the following result:

com.tian.my_code.test.designpattern.singleton.HungrySingleton@452b3a41
com.tian.my_code.test.designpattern.singleton.HungrySingleton@452b3a41
true

Copy the code

Hey, so we don’t have to create just one instance?

Answer:

Another method, the readObject0(false) method, is called in the readObject() method of the ObjectInputStream class. The checkResolve(readOrdinaryObject(unshared)) method is called in the readObject0(false) method.

There is this code in the readOrdinaryObject method:

Object obj;
try { 
     // If there are constructors, create instance if there are constructors
      obj = desc.isInstantiable() ? desc.newInstance() : null;
 } catch (Exception ex) {
 ... 
 }
// Determine if the singleton class has a readResolve method
if (desc.hasReadResolveMethod()) {
    Object rep = desc.invokeReadResolve(obj); 
}

/ / invokeReadResolve method
if(readResolveMethod ! =null) { 
    // calls readResolve in our singleton class and returns the object returned by that method
    // Note: this is a no-parameter method
     return readResolveMethod.invoke(obj, (Object[]) null);
}

Copy the code

Create an instance and then check to see if our singleton class has a readResolve method that takes no arguments

private Object readResolve(a){
        return INSTANCE;
}

Copy the code

conclusion

We override the readResolve() no-argument method to create only two instances of what appears to be one instance.

Next, the interviewer asks: Can enumeration singletons be serialized?

Can enumerated singletons be serialized?

Answer: can’t be destroyed, see me slowly give you a way.

Don’t talk,show me the code.

Let’s first verify that it really can’t be broken, look at the code:

public class EnumTest {

    public static void main(String[] args) {
        Singleton1 receives deserialized instances from the input stream
        EnumSingleton singleton1 = null;
        EnumSingleton singleton2 = EnumSingleton.getInstance();
        try {
            / / the serialization
            FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(singleton2);
            oos.flush();
            oos.close();

            // deserialize
            FileInputStream fis = new FileInputStream("EnumSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            singleton1 = (EnumSingleton) ois.readObject();
            ois.close();

            System.out.println(singleton1);
            System.out.println(singleton2);

            System.out.println(singleton1 == singleton2);

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

Running results:

INSTANCE
INSTANCE
true

Copy the code

Indeed, enumeration singletons are not corrupted by serialization, so why? Well, there’s got to be a reason.

Another method, the readObject0(false) method, is called in the readObject() method of the ObjectInputStream class. The checkResolve(readOrdinaryObject(unshared)) method is called in the readObject0(false) method.

 case TC_ENUM:
    return checkResolve(readEnum(unshared));

Copy the code

In the readEnum method

privateEnum<? > readEnum(boolean unshared) throws IOException {
        if(bin.readByte() ! = TC_ENUM) {throw newInternalError(); } Class<? > cl = desc.forClass();if(cl ! =null) {
            try {
                @SuppressWarnings("unchecked")
                / / the keyEnum<? > en = Enum.valueOf((Class)cl, name); result = en;/ /... Other code omitted}}}public static <T extends Enum<T>> valueOf(Class
       
         enumType, String name)
        {
       / / enumType. EnumConstantDirectory () returns a HashMap
       // Get from HashMap
        T result = enumType.enumConstantDirectory().get(name);
        if(result ! =null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
}
// Return a HashMap
 Map<String, T> enumConstantDirectory(a) {
        if (enumConstantDirectory == null) {
            T[] universe = getEnumConstantsShared();
            if (universe == null)
                throw new IllegalArgumentException(
                    getName() + " is not an enum type");
            // Use HashMap
            Map<String, T> m = new HashMap<>(2 * universe.length);
            for(T constant : universe) m.put(((Enum<? >)constant).name(), constant); enumConstantDirectory = m; }return enumConstantDirectory;
}

Copy the code

So, the enumeration singleton uses Map

, and the key of the Map is INSTANCE in our enumeration class. Because of the uniqueness of the Map’s key, a unique instance is then created. This enumerated singleton is also known as the registered singleton.
,>

This registered singleton pattern is also widely used in Spring, where the IOC container is a typical example.

conclusion

This paper describes the definition of singleton pattern, singleton pattern writing method. Singleton mode thread safety problem solving, reflection damage, deserialization damage, etc.

Note: Do not use design patterns for the sake of applying them. Rather, it’s natural to think of the single design pattern as a shortcut when you run into business problems.

Advantages and disadvantages of the singleton pattern

advantages

Having only one instance in memory reduces memory overhead. Multiple occupancy of resources can be avoided. Set global access points to strictly control access.

disadvantages

No excuses, poor scalability. If you want to extend a singleton, there is no other way but to modify the code.

The singleton pattern is inconsistent with the open – close principle.

knowledge

Summary of key knowledge of singleton pattern:

  • Privatized constructor

  • Ensuring thread safety

  • Lazy loading

  • Preventing reflex attacks

  • Prevent serialization and deserialization from breaking

If there is any gain, please click a like, see, also suggested to share with everyone to see the singleton pattern.