设计模式之单例模式

单例模式希望某个类负责创建自己的对象,并可以通过类直接访问,不需要实例化,实现方式有以下几种:

饿汉式

饿汉式既然都很饿了,那么希望创建成员变量时就实例化对象(便于理解记忆)
是线程安全的,但会造成内存浪费(无论是否使用都会实例化对象)

public class Hungry {
    // 私有的构造方法,不允许从外部实例化对象
    private Hungry(){}

    private static final Hungry INSTANCE = new Hungry();
    
    public static Hungry getInstance(){
        return INSTANCE;
    }
}

懒汉式

懒汉式就是“懒”,等到需要使用的时候才会实例化对象(方便记忆),也叫“延迟加载”。但这种方式在多线程情况下会存在问题。

public class Lazy {
    // 私有的构造犯法
    private Lazy(){}

    private static Lazy INSTANCE;

    public static Lazy getInstance(){
        // 判断是否已经实例化
        if(INSTANCE == null){
            INSTANCE = new Lazy();
        }
        return INSTANCE;
    }
}

第二种懒汉式,通过检测 + synchronized关键字实现

public class Lazy {
    // 私有的构造犯法
    private Lazy(){}
    // volatile 禁止指令重排
    private static volatile Lazy INSTANCE;

    // 锁住整个方法 synchronized
    public synchronized static Lazy getInstance(){
        if(INSTANCE == null){
            INSTANCE = new Lazy();
        }
        return INSTANCE;
    }
}

锁住整个方法会效率较低,所以改成锁住代码块。

public class Lazy {
    // 私有的构造犯法
    private Lazy(){}

    private static Lazy INSTANCE;

    public static Lazy getInstance(){
        if(INSTANCE == null) {
            // 锁的是 class 对象
            synchronized (Lazy.class){
                // 判断是否已经实例化
                if(INSTANCE == null){
                    INSTANCE = new Lazy();
                }
            }
        }
        return INSTANCE;
    }
}

第二种方式创建的懒汉式,似乎看起来在多线程情况下可以保证正确,但实则不然,第二种方式中new Lazy()看似只有一条命令,但在操作系统层面会转换成多条指令

  1. 分配对象的内存
  2. 初始化对象
  3. 设置引用指向分配的内存

正常的顺序会按照1->2->3指向,但new Lazy()不是原子化操作,会进行指令重排,即可能会按照1->3->2的顺序执行,那在多线程的情况下,可能会拿到还未初始化的对象。该问题可以通过volatile关键字解决。

public class Lazy {
    // 私有的构造犯法
    private Lazy(){}
    // volatile 禁止指令重排
    private static volatile Lazy INSTANCE;

    public static Lazy getInstance(){
        if(INSTANCE == null) {
            // 锁的是 class 对象
            synchronized (Lazy.class){
                // 判断是否已经实例化
                if(INSTANCE == null){
                    INSTANCE = new Lazy();
                }
            }
        }
        return INSTANCE;
    }
}

但你以为加上volatile后就真的安全了吗?也不尽然,依旧可以通过反射操作获取两个不一样的对象。

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        // 获取单例对象
        Lazy instance1 = Lazy.getInstance();

        Class<? extends Lazy> instance1Class = instance1.getClass();
        Constructor<? extends Lazy> declaredConstructor = instance1Class.getDeclaredConstructor(null);
        // 私有的构造方法设置为可以访问
        declaredConstructor.setAccessible(true);
        // 通过构造方法创建对象
        Lazy instance2 = declaredConstructor.newInstance();
        // 判断是否为同一个对象
        System.out.println(instance1 == instance2);
    }

静态内部类

public class StaticInnerClass {
    // 私有构造方法
    private StaticInnerClass(){}

    // 静态内部类
    private static final class InnerClass{
        private static final StaticInnerClass INSTANCE = new StaticInnerClass();
    }

    public static StaticInnerClass getInstance(){
        return InnerClass.INSTANCE;
    }
}

枚举

上面提到懒汉式中可以用反射破坏单例模式,其实在饿汉式和静态内部类的实现方法中也能通过反射破坏单例模式,所以可以使用枚举实现单例模式,并且不会被反射破坏。

public enum EnumClass {

    // 单例变量
    INSTANCE;

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

可以通过打印构造函数查看:

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

        Class<? extends EnumClass> instanceClass = instance.getClass();

        Constructor<?>[] declaredConstructors = instanceClass.getDeclaredConstructors();
        for(Constructor<?> constructor : declaredConstructors){
            System.out.println(constructor);
        }
    }

输出结果为:private EnumClass(java.lang.String,int)

尝试通过反射创建对象:

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumClass instance = EnumClass.getInstance();

        Class<? extends EnumClass> instanceClass = instance.getClass();

        // 查看构造函数
//        Constructor<?>[] declaredConstructors = instanceClass.getDeclaredConstructors();
//        for(Constructor<?> constructor : declaredConstructors){
//            System.out.println(constructor);
//        }
        // 企图通过反射创建新的对象
        Constructor<? extends EnumClass> declaredConstructor = instanceClass.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        EnumClass instance2 = declaredConstructor.newInstance("", 0);

    }

结果会抛出异常:java.lang.IllegalArgumentException: Cannot reflectively create enum objects,表面枚举不能被反射破坏。

posted @ 2021-03-29 20:48  自由自在的卷卷  阅读(40)  评论(0)    收藏  举报