This article has been included in my Github address: github.com/allentofigh… , welcome everyone to pay attention and give a STAR, this is very important to me, thank you for your support! After the code sea each article will be included in this address to facilitate everyone to consult!

preface

The singleton pattern is the simplest and most basic design pattern. Even for a beginner developer, when asked which design patterns he has used, he will probably say singleton. But do you think the basic singleton pattern is really that simple? You might ask, “What about a simple singleton pattern?” Ha ha, no more words, let us wait and see, adhere to see, I believe you will have harvest!

The hungry type

Hunger-style is the most common and least concerned singleton pattern because it does not have thread-safety issues. Hunger-style creates instance objects as soon as the class is loaded. The hungry Chinese is written as follows:

public class SingletonHungry {
    private static SingletonHungry instance = new SingletonHungry();

    private SingletonHungry(a) {}private static SingletonHungry getInstance(a) {
        returninstance; }}Copy the code
  • The test code is as follows:
class A {
    public static void main(String[] args) {
        IntStream.rangeClosed(1.5)
                .forEach(i -> {
                    new Thread(
                            () -> {
                                SingletonHungry instance = SingletonHungry.getInstance();
                                System.out.println("instance = "+ instance); } ).start(); }); }}Copy the code

Advantages: Thread-safe, no need to worry about concurrency, and the simplest to write.

Disadvantages: The object is created when the class is loaded, which means that the object will be created regardless of whether you use it or not, wasting memory

LanHanShi

Type of writing, the following is the most basic a starving man, in a single thread, this way is very perfect, but our actual program execution basic it can’t be a single thread, so this kind of writing will thread safety problems

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

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

Demonstrating multithreaded execution

class B {
    public static void main(String[] args) {
        IntStream.rangeClosed(1.5)
                .forEach(i -> {
                    new Thread(
                            () -> {
                                SingletonLazy instance = SingletonLazy.getInstance();
                                System.out.println("instance = "+ instance); } ).start(); }); }}Copy the code

As a result, it is clear that the obtained instance object is not a singleton. That is, it is not thread-safe and therefore cannot be used in multithreaded situations

DCL (Double check lock)

DCL Double Check Lock is a Double Check when creating an instance, first Check whether the instance object is empty, if not, Lock the current class, then Check whether the instance is empty, if still empty, create the instance. The code is as follows:

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

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

The test code is as follows:

class C {
    public static void main(String[] args) {
        IntStream.rangeClosed(1.5)
                .forEach(i -> {
                    new Thread(
                            () -> {
                                SingleTonDcl instance = SingleTonDcl.getInstance();
                                System.out.println("instance = "+ instance); } ).start(); }); }}Copy the code

If it is empty, then the Class of the object is used as a lock, so that only one thread can access the object at a time. Then again, determine whether the instance object is empty. Finally, the instantiation object is actually initialized. It may seem like it’s all right, but once you’ve learned about the JVM you’ll probably see it at a glance. Instance = new SingleTonDcl(); Since this is not an atomic operation, this sentence is executed in three steps at the JVM level:

Allocate memory space for SingleTonDcl. 2. Initialize SingleTonDcl instance 3. Reference instance object to allocated memory space (instance is null)

Normally the above three steps are executed sequentially, but in practice the JVM may have to optimize our code in a random order of 1, 3, and 2, as shown in the code below

public static SingleTonDcl getInstance(a) {
    if (null == instance) {
        synchronized (SingleTonDcl.class) {
            if (null == instance) {
                1.Allocate memory space to SingleTonDcl3.Refers an instance object to the allocated memory space (instance is notnullA)2.Initialize SingleTonDcl instance}}}return instance;
}
Copy the code

Let’s say I have two threads, T1, t2

  1. If T1 is suspended after performing step 3 above
  2. If (null == instance) t2 then enters the getInstance method. T1 performs step 3 and instance is not null. Instance is returned directly, but since T1 has not performed step 2, instance is actually a work in progress, which will lead to unpredictable risks!

Since the problem is that the instruction can be reordered, why not not make it reordered? That’s what volatile is for. We could add a volatile modifier to the instance variable

Voiceover: What volatile does 1. Ensure object memory visibility 2. Prevents instruction reorderingCopy the code

The optimized code looks like this

public class SingleTonDcl {
    private SingleTonDcl(a) {}// Add the volatile keyword in front of the object
    volatile private static SingleTonDcl instance = null;

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

At this point, the problem seems to be solved, and the double locking + volatile mechanism does in fact basically solve the thread-safety problem, ensuring “true” singletons. But is that really the case? Keep reading

Static inner class

Look at the code

public class SingleTonStaticInnerClass {
    private SingleTonStaticInnerClass(a) {}private static class HandlerInstance {
        private static SingleTonStaticInnerClass instance = new SingleTonStaticInnerClass();
    }

    public static SingleTonStaticInnerClass getInstance(a) {
        returnHandlerInstance.instance; }}Copy the code
  • The test code is as follows:
class D {
    public static void main(String[] args) {
        IntStream.rangeClosed(1.5)
                .forEach(i->{
                    new Thread(()->{
                        SingleTonStaticInnerClass instance = SingleTonStaticInnerClass.getInstance();
                        System.out.println("instance = "+ instance); }).start(); }); }}Copy the code

Static inner class features:

This approach uses the JVM class loading mechanism to ensure thread safety; Since SingleTonStaticInnerClass is private, in addition to the getInstance () there is no way to access it, so it is LanHanShi; There is no synchronization when reading instances at the same time, no performance defects; Nor does it depend on the JDK version;

Still, it’s not perfect.

Unsafe singleton

None of the above implementation singletons is perfect for two main reasons

1. Reflex attacks

First of all, we will mention Java’s love-hate reflection mechanism. Without further ado, we will go directly to the side of the code. Here we will use DCL as an example (why choose DCL because many people think that the DCL is the best written…. This is where we go to “punch them in the face”)

Modify the above DCl test code as follows:

class C {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class<SingleTonDcl> singleTonDclClass = SingleTonDcl.class;
        // Get the constructor for the class
        Constructor<SingleTonDcl> constructor = singleTonDclClass.getDeclaredConstructor();
        // Set constructor private permissions
        constructor.setAccessible(true);
        // Reflection create instance (); reflection create instance (); reflection create instance ();
        // Since the instance has been created in the normal way, it will enter if
        SingleTonDcl instance = constructor.newInstance();
        The normal way to get an instance is after the reflection creation instance, so that when reflection is successfully created, the reference in the singleton is actually empty for the reflection attack to succeed
        SingleTonDcl instance1 = SingleTonDcl.getInstance();
        System.out.println("instance1 = " + instance1);
        System.out.println("instance = "+ instance); }}Copy the code

Two objects! Is your heart at peace? It’s not what you thought it was, is it? The other methods are basically similar and can destroy singletons through reflection.

2. Serialization attacks

The serialization and deserialization attack code is shown in the example of “Hanchman singleton”. First, add code to the corresponding class of hanchman singleton implementing Serializable interface.

public class SingletonHungry implements Serializable {
    private static SingletonHungry instance = new SingletonHungry();

    private SingletonHungry(a) {}private static SingletonHungry getInstance(a) {
        returninstance; }}Copy the code

And then how do you attack with serialization and deserialization

SingletonHungry instance = SingletonHungry.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file")));
// serialize the write operation
oos.writeObject(instance);
File file = new File("singleton_file");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))
// Deserialize the read operation
SingletonHungry newInstance = (SingletonHungry) ois.readObject();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
Copy the code

So let’s see what happens

Sure enough, there were two different objects! The solution to this deserialization attack is simply to override the readObject method to be called during deserialization

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

In this way, only one instance is read in deserialization, ensuring the implementation of singleton.

Truly safe singleton: enumeration mode

public enum SingleTonEnum {
    /** * instance object */
    INSTANCE;
    public void doSomething(a) {
        System.out.println("doSomething"); }}Copy the code

A method is called

public class Main {
    public static void main(String[] args) { SingleTonEnum.INSTANCE.doSomething(); }}Copy the code

The singleton implemented by the enumeration pattern is the true singleton pattern and is the perfect implementation

One might wonder: Can enumerations also break their singleton implementations through reflection?

Try modifying enumerated test classes

class E{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class<SingleTonEnum> singleTonEnumClass = SingleTonEnum.class;
        Constructor<SingleTonEnum> declaredConstructor = singleTonEnumClass.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        SingleTonEnum singleTonEnum = declaredConstructor.newInstance();
        SingleTonEnum instance = SingleTonEnum.INSTANCE;
        System.out.println("instance = " + instance);
        System.out.println("singleTonEnum = "+ singleTonEnum); }}Copy the code

No no-parameter construction? Let’s use the Javap tool to look up the bytecode and see what’s going on

Well, there’s a String Int constructor with arguments, so let’s try it out

// Get the constructor to look like this
Constructor<SingleTonEnum> declaredConstructor = singleTonEnumClass.getDeclaredConstructor(String.class,int.class);
Copy the code

Exception: Cannot reflectively create enum objects

There is no secret in the source code, so what does newInstance() actually do? Why does creating an enumeration with reflection throw such an exception?

The truth is out! If it is an enumeration, it is not allowed to be created by reflection, which is why it is really safe to create singletons using enums!

conclusion

Above is some knowledge about the singleton pattern, you really don’t look down upon this little singleton, the interview most candidates write wrong so a simple singletons, writing for the majority of also only DCL, but ask whether have what not safe, how to write secure use enum singleton, almost no one can answer it! Some people say you can write DCL, but why bother? But WHAT I want to say is that it is precisely this kind of spirit that enables you to gradually accumulate technical depth and become an expert. You have the persistence to explore the technology. Why can’t you worry about becoming an expert?

Finally, welcome everyone to pay attention to my official number, add my friend: “Geekoftaste”, communicate together, common progress!