Singleton Mode: In-depth analysis from hungry and lazy to enumeration




1.1 Hungry Chinese

public class HungryMan {
    
    
    private HungryMan() {
    
    
    }

    private final static HungryMan hungryMan = new HungryMan();

    public static HungryMan getInstance() {
    
    
        return hungryMan;
    }
}

Disadvantage: waste of memory.




1.2, lazy man

public class LazyMan {
    
    
    private LazyMan() {
    
    
    }

    private static LazyMan lazyMan;

    public static LazyMan getInstance(){
    
    
        if (lazyMan == null){
    
    
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
}

Disadvantages: cannot be multi-threaded concurrently.




1.3, DCL lazy style

public class LazyMan {
    
    
    private LazyMan() {
    
    
    }

    private static LazyMan lazyMan;

    //双重检测锁的 懒汉模式
    public static LazyMan getInstance() {
    
    
        if (lazyMan == null) {
    
    
            synchronized (LazyMan.class) {
    
    
                if (lazyMan == null) {
    
    
                    lazyMan = new LazyMan(); }
            }
        }
        return lazyMan;
    }
}

Disadvantages: thread is not safe.

The specific reason: JVM is divided into three steps when executing code line 12:

  1. Allocate memory space.
  2. Execute the constructor and initialize the object.
  3. Point this object to this space.

In order to optimize program performance, jvm reorders the program execution sequence.

Therefore, the above execution process may become 1 3 2. This will cause the lazyMan object on line 12 to be not null when it is not initialized.


Correct way of writing : Add the volatile keyword to prohibit jvm reordering.

public class LazyMan {
    
    
    private LazyMan() {
    
    
    }

    private volatile static LazyMan lazyMan;

    public static LazyMan getInstance() {
    
    
        if (lazyMan == null) {
    
    
            synchronized (LazyMan.class) {
    
    
                if (lazyMan == null) {
    
    
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }
}






2. The static inner class implements a singleton

public class Holder {
    
    
    private Holder() {
    
    
    }
    
    public static Holder getInstance() {
    
    
        return InnerClass.holder;
    }
    
    public static class InnerClass {
    
    
        private static final Holder holder = new Holder();
    }
}





3. The reflection mechanism destroys the common singleton mode

3.1, attack 1
import java.lang.reflect.Constructor;

public class Singleton {
    
    
    private Singleton() {
    
    
    }

    private static Singleton singleton;

    public static Singleton getInstance() {
    
    
        synchronized (Singleton.class) {
    
    
            if (singleton == null) {
    
    
                singleton = new Singleton();
            }
        }
        return singleton;
    }


    public static void main(String[] args) throws Exception {
    
    
        Singleton s1 = Singleton.getInstance();

        Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
        //指示反射的对象在使用时应该取消 Java 语言访问检查,使得私有的构造函数能够被访问
        constructor.setAccessible(true);
        Singleton s2 = constructor.newInstance();

        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());
    }
}

Output result:

1163157884

1956725890

The results show that s1 and s2 are two different instances.



3.2 Resistance

If you want to resist this attack, you must prevent the constructor from being successfully called twice. The number of instantiations needs to be counted in the constructor, and an exception is thrown if it is greater than once. You can also set the key and judge whether the keys are consistent in the construction method.

public class Singleton {
    
    
    private static Singleton singleton;

    private static int count = 0;

    private Singleton() {
    
    
        synchronized (Singleton.class) {
    
    
            if (count > 0) {
    
    
                throw new RuntimeException("单例禁止反射");
            }
            count++;
        }
    }

    public static Singleton getInstance() {
    
    
        synchronized (Singleton.class) {
    
    
            if (singleton == null) {
    
    
                singleton = new Singleton();
            }
        }
        return singleton;
    }
}

Output result:

Caused by: java.lang.RuntimeException: Singleton prohibits reflection



3.3, attack 2
    public static void main(String[] args) throws Exception {
    
    
        Field count = Singleton.class.getDeclaredField("count");

        Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton s1 = constructor.newInstance();
        count.set(singleton,0);
        Singleton s2 = constructor.newInstance();

        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());
    }

Output result:

1956725890

356573597

The results show that s1 and s2 are two different instances.




3.4 Summary of reflection destruction singleton

The road is one foot high, the magic is one foot high

	私有化构造器并不保险,无法避免反射的恶意攻击。

	因此推荐使用枚举单例模式。枚举单例有序列化和线程安全的保证,而且只要几行代码就能实现是单例最好的的实现方式





4. Enumeration reflection

Code:

public enum  Singleton {
    
    
    INSTANCE;

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

Idea decompilation:

public enum Singleton {
    
    
    INSTANCE;

    private Singleton() {
    
    
    }

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

cmd decompilation:

D:\IdeaProjects\Test\out\production\Test>javap -p Singleton.class
Compiled from "Singleton.java"
public final class Singleton extends java.lang.Enum<Singleton> {
  public static final Singleton INSTANCE;
  private static final Singleton[] $VALUES;
  public static Singleton[] values();
  public static Singleton valueOf(java.lang.String);
  private Singleton();
  public static Singleton getInstance();
  static {};
}

In fact, the results of decompilation are all wrong. The constructor is not parameterless.

Finally, use the jad tool to decompile

D:\IdeaProjects\Test\out\production\Test>jad -sjava Singleton.class
Parsing Singleton.class... Generating Singleton.java
public final class Singleton extends Enum
{
    
    

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

    public static Singleton valueOf(String name)
    {
    
    
        return (Singleton)Enum.valueOf(Singleton, name);
    }

    private Singleton(String s, int i)
    {
    
    
        super(s, i);
    }

    public static Singleton getInstance()
    {
    
    
        return INSTANCE;
    }

    public static final Singleton INSTANCE;
    private static final Singleton $VALUES[];

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

From the 14th line of code, we can see that actually the parameters of the construction method (String s, int i)

test

public static void main(String[] args) throws Exception {
    
    
        Singleton s1 = Singleton.getInstance();
        //源码构造方法参数 (String,int)
        Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(String.class,int.class);
        constructor.setAccessible(true);
        Singleton s2 = constructor.newInstance();

        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());
    }

Output result

java.lang.IllegalArgumentException: Cannot reflectively create enum objects


The result proves that enumeration can solve thread safety problems, avoid reflection and serialization problems.





Learn about other design patterns

Guess you like

Origin blog.csdn.net/qq_44972847/article/details/108057553