单例模式

饿汉式单例

可能造成空间的浪费

// 饿汉式单例
public class Hungry {
	// 私有构造器
    private Hungry(){
    }
	// 先实例化对象
    private final static Hungry HUNGRY = new Hungry();

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

懒汉式单例

// 懒汉式单例
public class Lazy {
    // 私有构造器
    private Lazy(){
    }
    // 先不实例化
    private static Lazy LAZY;

    public static Lazy getInstance(){
        // 先判断再实例化
        if (LAZY == null) {
            LAZY = new Lazy();
        }
        return LAZY;
    }
}

以上这种单例在单线程的情况下是可以的,但是如果是多线程的情况单例模式就会被破坏,所以我们要加锁如下

// 懒汉式单例
public class Lazy {
    // 私有构造器
    private Lazy(){
    }
    // 先不实例化
    private static Lazy LAZY;

    public static Lazy getInstance(){
        // 一重判断
        if (LAZY == null) {
            // 加锁
            synchronized (Lazy.class){
                // 二重判断后再实例化
                if (LAZY == null) {
                    LAZY = new Lazy();
                }
            }
        }
        return LAZY;
    }
}

以上这样做确实可以在多线程的情况下防止单例被破坏,但是在极端的情况下,我们的实例化操作LAZY = new Lazy();不是一个原子性的操作。实例化的过程是先分配空间,通过构造器实例化对象,再将对象指向地址空间。这三步操作的指令顺序是有可能改变的,如果是先分配空间还没实例化对象就直接指向该地址空间,该空间此时是空的,如果这时候一条线程进来会判断LAZY != null直接返回对象,造成问题。所以我们还要加上volatile来保证有序性指令不会重排序。

// DCL懒汉式单例
public class Lazy {
    // 私有构造器
    private Lazy(){
    }
    // 加上volatile
    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 lazy1 = Lazy.getInstance();
    // 通过反射获取无参构造
    Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
    // 将无参构造私有破坏掉
    declaredConstructor.setAccessible(true);
    // 通过反射实例化对象
    Lazy lazy2 = declaredConstructor.newInstance();
    System.out.println(lazy1);
    System.out.println(lazy2);
}

输出的结果不是同一个
结果
我们可以通过再无参构造里面进行判断抛出异常来解决,但是如果两个对象都是通过反射实例化,我们则需要加一个私有变量进行判断

// 可以用加密算法生成变量
private static Boolean askjdhsda = false;
// 私有构造器
private Lazy(){
    synchronized (Lazy.class){
        if (askjdhsda == false){
            askjdhsda = true;
        } else {
            throw new RuntimeException();
        }
    }
}

这样做可以防止单例被破坏,但是还是有可能通过反编译和解密发现这个变量,再通过反射将我们新定义的变量值改变,通过newInstance()源码

public T newInstance(Object... var1) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        if (!this.override && !Reflection.quickCheckMemberAccess(this.clazz, this.modifiers)) {
            Class var2 = Reflection.getCallerClass();
            this.checkAccess(var2, this.clazz, (Object)null, this.modifiers);
        }

        if ((this.clazz.getModifiers() & 16384) != 0) {
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        } else {
            ConstructorAccessor var4 = this.constructorAccessor;
            if (var4 == null) {
                var4 = this.acquireConstructorAccessor();
            }

            Object var3 = var4.newInstance(var1);
            return var3;
        }
    }

我们发现使用枚举可以防止反射破坏单例,下面是一个验证

public enum EnumSingleton {
    INSTANCE;
    public static EnumSingleton getInstance(){
        return INSTANCE;
    }
}

class Test{
    public static void main(String[] args) throws Exception {
        Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        EnumSingleton enumSingleton1 = declaredConstructor.newInstance();
        EnumSingleton enumSingleton2 = declaredConstructor.newInstance();
        System.out.println(enumSingleton1);
        System.out.println(enumSingleton2);
    }
}

得到结果抛出了一个异常Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects所以使用枚举,反射也不能破坏单例模式

©️2020 CSDN 皮肤主题: 游动-白 设计师:上身试试 返回首页