Design Pattern Study Notes - Have you really learned the singleton pattern?

Have you really learned the singleton pattern?

I. Overview

Singleton Pattern refers to ensuring that a class has absolutely only one instance under any circumstances and provides a global access point. It is a creational design pattern.

2. Class diagram

Insert image description here

3. Common writing methods

public class SingletonTest {
    
    

    public static void main(String[] args) {
    
    
        Singleton instance = Singleton.getInstance();
    }

    static class Singleton {
    
    
        private static final Singleton instance = new Singleton();

        private Singleton() {
    
    

        }

        public static Singleton getInstance() {
    
    
            return instance;
        }
    }
}

4. Issues related to singleton mode

4.1 Disadvantages of Hungry-style singleton writing

The Hungry-style singleton writing method is initialized immediately when the class is loaded, and the singleton object is created.

It is absolutely thread-safe and is instantiated before the thread appears, so there is no access security issue.

There is another way to write Hungry-style singleton, the code is as follows:

public class SingletonTest {
    
    

    public static void main(String[] args) {
    
    
        Singleton instance = Singleton.getInstance();
    }

    static class Singleton {
    
    
        private static final Singleton instance;

        static {
    
    
            instance = new Singleton();
        }

        private Singleton() {
    
    

        }

        public static Singleton getInstance() {
    
    
            return instance;
        }
    }
}

This writing method uses the mechanism of static blocks, which is very simple and easy to understand. The Hungry-style singleton writing method is suitable for situations where there are few singleton objects.

Writing this way can ensure absolute thread safety and relatively high execution efficiency. But its shortcoming is also obvious, that is, all object classes are instantiated when loaded. In this way, if there are a large number of singleton objects in the system, and the number of singleton objects is uncertain, a large amount of memory will be wasted during system initialization, resulting in uncontrollable system memory.

4.2 Lazy style singleton writing

class LazySingleton {
    
    
    private LazySingleton() {
    
    
    }

    private static LazySingleton lazy = null;

    public static LazySingleton getInstance() {
    
    
        if (lazy == null) {
    
    
            lazy = new LazySingleton();
        }
        return lazy;
    }
}

If two threads enter the getInstance() method at the same time, the if(null== instance) condition will be met at the same time and two objects will be created. If both threads continue to execute the following code, it is possible that the results of the thread executed later will overwrite the results of the thread executed first.

So how to solve it?

Add the keyword to the getInstance() method to make this method a thread synchronization method. synchronized

class LazySingleton {
    
    
    private LazySingleton() {
    
    
    }

    private static LazySingleton lazy = null;

    public synchronized static LazySingleton getInstance() {
    
    
        if (lazy == null) {
    
    
            lazy = new LazySingleton();
        }
        return lazy;
    }
}

Although this can perfectly solve the problem, if synchronized locking is used when the number of threads increases sharply, it will cause a large number of threads to be blocked, resulting in a significant decrease in program performance.

Then we transform it into Double check lock singleton writing method:

class LazySingleton {
    
    
    private LazySingleton() {
    
    
    }

    private static LazySingleton lazy = null;

    public static LazySingleton getInstance() {
    
    
        //检查是否要创建
        if (lazy == null) {
    
    
            synchronized (LazySingleton.class) {
    
    
                //再次检查,避免有多个线程同时进入上面的if代码块,避免指令重排序问题
                if (lazy == null) {
    
    
                    lazy = new LazySingleton();
                }
            }
        }
        return lazy;
    }
}

So is this solution perfect?

Actually no, we can also forcibly create different instance objects through reflection

public static void main(String[] args) {
    
    
    try {
    
    
        //通过反射获取构造方法
        Constructor constructor = LazySingleton.class.getDeclaredConstructor(null);
        //强制访问私有方法
        constructor.setAccessible(true);
        Object o1 = constructor.newInstance();
        Object o2 = constructor.newInstance();
        System.out.println(o1==o2);//结果为false,单例失效
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
}

Next is the ultimate singleton method, which uses Java syntax to not load internal classes by default:

public class LazyStaticInnerClassSingleton {
    
    

    private LazyStaticInnerClassSingleton() {
    
    
        if (LazyHolder.INSTANCE != null) {
    
    
            throw new RuntimeException("休想通过特殊手段调用我的私有构造");
        }
    }

    private static LazyStaticInnerClassSingleton getInstance() {
    
    
        return LazyHolder.INSTANCE;
    }

    /**
     * 默认不加载内部类
     */
    private static class LazyHolder {
    
    
        private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();
    }
}

So is this really the ultimate method? In fact, there are also some naughty operations.

Enumeration singleton writing method:

public enum EnumSingleton {
    
    
    INSTANCE;

    private Object data;

    public Object  getData(){
    
    
        return data;
    }

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

    public static EnumSingleton getInstance(){
    
    
        return INSTANCE;
    }
    
}
public static void main(String[] args) {
    
    
    EnumSingleton instance1 = EnumSingleton.getInstance();
    EnumSingleton instance2 = EnumSingleton.getInstance();
    System.out.println(instance1 == instance2); //结果为true
}

I really don’t want to analyze the reasons anymore, I’m so bald…

Decompile the EnumSingleton source code and you can find:

Insert image description here

It turns out that the enumeration-style singleton writing method assigns INSTANCE in the static block, which is the implementation of the Hungry-style singleton writing method.

So at this point, is there any way to destroy the singleton? Actually there is! ! !

The enumeration-style singleton writing can be destroyed by deserialization:

After a singleton object is created, sometimes it is necessary to serialize the object and write it to the disk. When it is used next time, the object is read from the disk and deserialized to convert it into a memory object. The deserialized object will reallocate memory, that is, recreate it. If the serialized target object is a singleton object, it violates the original intention of the singleton mode and is equivalent to destroying the singleton mode.

So, how to ensure that the singleton pattern can be implemented even in the case of serialization? It's actually very simple, just add the readResolve() method.

As for the reason, go check the source code yourself, I won’t tell you anymore!

Guess you like

Origin blog.csdn.net/qq_27574367/article/details/131436488