Loading

设计模式之单例模式Single

饿汉式单例

package com.zhang.single;

// 饿汉式单例
public class Hungry {

    private Hungry() {

    }

    private final static Hungry HUNGRY = new Hungry();

    public static Hungry getInstance() {
        return HUNGRY;
    }
}

懒汉式单例

  1. 单线程下的懒汉式单例

    package com.zhang.single;
    
    public class LazyMan {
        private LazyMan() {
    
        }
    
        public static LazyMan lazyMan;
    
        public static LazyMan getInstance() {
            if (lazyMan == null) {
                lazyMan = new LazyMan();
            }
            return lazyMan;
        }
    }
    
  2. 多线程下的懒汉式单例偶尔会失败

    package com.zhang.single;
    
    // 普通懒汉式单例
    public class LazyMan {
        private LazyMan() {
            System.out.println(Thread.currentThread().getName() + "ok");
        }
    
        private static LazyMan lazyMan;
    
        public static LazyMan getInstance() {
            if (lazyMan == null) {
                lazyMan = new LazyMan();
            }
            return lazyMan;
        }
    
        // 多线程并发测试
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                new Thread(()->{
                    LazyMan.getInstance();
                }).start();
            }
        }
    }
    
  3. 双重检测锁模式的懒汉式单例 DCL懒汉式

    在极端模式下,lazy = new Lazy(); 不是一个原子性操作

    1、分配内存空间
    2、执行构造方法,初始化对象
    3、把这个对象指向这个空间
    此操作可能会导致指令重排

    package com.zhang.single;
    
    // 双重检测锁模式的懒汉式单例    DCL懒汉式
    public class LazyMan {
        private LazyMan() {
            System.out.println(Thread.currentThread().getName() + "ok");
        }
    
        private volatile static LazyMan lazyMan;
    
        public static LazyMan getInstance() {
            if (lazyMan == null) {
                synchronized (LazyMan.class) {
                    if (lazyMan == null) {
                        lazyMan = new LazyMan();
                    }
                }
            }
            return lazyMan;
        }
    }
    

静态内部类单例

package com.zhang.single;

// 静态内部类
public class Holder {
    private Holder() {

    }

    public static Holder getInstance() {
        return InnerClass.HOLDER;
    }

    public static class InnerClass {
        private static final Holder HOLDER = new Holder();
    }
}

上述这几种单例模式都是不安全的,可以通过反射破坏

下面通过构造器获取一个对象,和通过反射获取一个对象,明显两者不是同一个,单例模式破坏

package com.zhang.single;

import java.lang.reflect.Constructor;

// 通过反射破坏双重检测锁懒汉式单例
public class Lazy {

    private Lazy() {
        System.out.println(Thread.currentThread().getName() + "启动");
    }

    private volatile static Lazy lazy;

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

    public static void main(String[] args) throws Exception {
        // 正常获取一个对象
        Lazy instance = Lazy.getInstance();
        // 通过反射获取构造器
        Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
        // 暴力破解
        declaredConstructor.setAccessible(true);
        // 通过反射获取的构造器new一个实例对象
        Lazy lazy = declaredConstructor.newInstance();
        // 测试两个是否相同
        System.out.println("instance == lazy:" + (instance == lazy));
    }
}

解决办法:在构造器中加入一个判断,并抛出异常。(三重检测)

private Lazy() {
    if (lazy != null) {
        throw new RuntimeException("不要试图用反射破坏异常");
    }
}

但是,在上述单例模式中,两个对象都是通过反射获得,仍然会破坏单例模式

public static void main(String[] args) throws Exception {
    // 通过反射获取构造器
    Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
    // 暴力破解
    declaredConstructor.setAccessible(true);
    // 通过反射获取的构造器new两个实例对象
    Lazy lazy1 = declaredConstructor.newInstance();
    Lazy lazy2 = declaredConstructor.newInstance();
    // 测试两个是否相同
    System.out.println("instance == lazy:" + (lazy1 == lazy2));
}

解决办法:在类中加入一个静态变量标志位,设置为false,以此来判断。

private static boolean zhang = false;

private Lazy() {
    if (zhang == false) {
        zhang = true;
    } else {
        throw new RuntimeException("不要试图用反射破坏异常");
    }
}

但是,道高一尺魔高一丈,这些方法依旧不是安全的。如:通过将标志位私有权限破坏,仍然可以破坏单例模式。

枚举自带单例模式

通过此方法得到的结果为true

package com.zhang.single;

// enum枚举本身也是一个class类
public enum EnumSingle {
    INSTANCE;

    public EnumSingle getInstance() {
        return INSTANCE;
    }
}

class Test {
    public static void main(String[] args) {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        EnumSingle instance2 = EnumSingle.INSTANCE;
        System.out.println("(instance1 与 instance2) = " + (instance1 == instance2));
    }
}

通过反射尝试破坏该枚举类

package com.zhang.single;

import java.lang.reflect.Constructor;

// enum枚举本身也是一个class类
public enum EnumSingle {
    INSTANCE;

    public EnumSingle getInstance() {
        return INSTANCE;
    }
}

class Test {
    public static void main(String[] args) throws Exception {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println("(instance1 与 instance2) = " + (instance1 == instance2));
    }
}

运行结果如下,告诉我们没有这个空参构造方法

Exception in thread "main" java.lang.NoSuchMethodException: com.zhang.single.EnumSingle.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.getDeclaredConstructor(Class.java:2178)
	at com.zhang.single.Test.main(EnumSingle.java:17)

只能通过jad.exe反编译工具,将该类文件反编译成为一个java文件

发现该java文件中有一个有参构造器

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

根据上述代码,继续尝试破坏

class Test {
    public static void main(String[] args) throws Exception {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println("(instance1 与 instance2) = " + (instance1 == instance2));
    }
}

运行结果:

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
	at com.zhang.single.Test.main(EnumSingle.java:19)

结论:枚举单例确实不能被破坏

posted @ 2021-04-17 20:41  Zzxij  阅读(73)  评论(0)    收藏  举报