单例模式的实现方法

实现1

    // 问题1:为什么加 final
    // 问题2:如果实现了序列化接口, 还要做什么来防止反序列化破坏单例
    public final class Singleton implements Serializable {
        // 问题3:为什么设置为私有? 是否能防止反射创建新的实例?
        private Singleton() {
        }

        // 问题4:这样初始化是否能保证单例对象创建时的线程安全?
        private static final Singleton INSTANCE = new Singleton();

        // 问题5:为什么提供静态方法而不是直接将 INSTANCE 设置为 public, 说出你知道的理由
        public static Singleton getInstance() {
            return INSTANCE;
        }

        public Object readResolve() {
            return INSTANCE;
        }
    }

问题一:
因为防止子类重写方法,而破坏单例
问题二:
反序列化会创建一个新的对象,从而破坏单例。这就需要写一个readResolve方法,这个方法会再JVM反序列化的时候调用。所以即使反序列化,主要有这个方法,依然会返回当前这个单例对象
问题三:
1.私有构造方法是为了防止通过构造器破坏单例
2.私有构造器不能防止反射破坏单例(反射怎么破坏?学完回来补充)
问题四:
没用线程安全问题。因为加了static,就会在类加载的时候创建,JVM会保证线程安全
问题五:
1.面向对象封装的思想
2.可以方便后期修改(比如把现在饿汉式的变成懒汉式的)

实现2

在这里插入图片描述

问题一:
枚举类的枚举,再反编译后相当于是个静态的成员变量,所以INSTANCE对象相当于是个静态成员变量,当然就是单例的。
问题二:
应为INSTANCE是静态的,所以也是再类加载的时候由JVM保证线程安全
问题三:
不会被反射破坏。(原因看下面)
问题四:
不会被反序列化破坏(原因看下面)
问题五:
因为枚举对象时静态成员变量,所以时饿汉式的
问题六:
将初始化逻辑写在枚举类的构造方法中

为什么枚举的单例不会被反射破坏

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

    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        EnumSingleton singleton1=EnumSingleton.INSTANCE;
        EnumSingleton singleton2=EnumSingleton.INSTANCE;
        System.out.println("正常情况下,实例化两个实例是否相同:"+(singleton1==singleton2));
        Constructor<EnumSingleton> constructor= null;
        constructor = EnumSingleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        EnumSingleton singleton3= null;
        singleton3 = constructor.newInstance();
        System.out.println(singleton1+"\n"+singleton2+"\n"+singleton3);
        System.out.println("通过反射攻击单例模式情况下,实例化两个实例是否相同:"+(singleton1==singleton3));
    }
}

以上使用反射攻击单例的代码种会在constructor = EnumSingleton.class.getDeclaredConstructor();,报 1 Exception in thread "main" java.lang.NoSuchMethodException: com.lxp.pattern.singleton.EnumSingleton.<init>()的错误,这就说明,枚举类因为没用无参构造方法,所以不能被攻击。
但是,枚举类可以获取父类的参数为(String.class,int.class)的构造器,那用这个构造器能否攻击?

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

    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        EnumSingleton singleton1=EnumSingleton.INSTANCE;
        EnumSingleton singleton2=EnumSingleton.INSTANCE;
        System.out.println("正常情况下,实例化两个实例是否相同:"+(singleton1==singleton2));
        Constructor<EnumSingleton> constructor= null;
//        constructor = EnumSingleton.class.getDeclaredConstructor();
        constructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);//其父类的构造器
        constructor.setAccessible(true);
        EnumSingleton singleton3= null;
        //singleton3 = constructor.newInstance();
        singleton3 = constructor.newInstance("testInstance",66);
        System.out.println(singleton1+"\n"+singleton2+"\n"+singleton3);
        System.out.println("通过反射攻击单例模式情况下,实例化两个实例是否相同:"+(singleton1==singleton3));
    }
}

还是会在 singleton3 = constructor.newInstance("testInstance",66);,报Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects的错误。
进入源码

@CallerSensitive
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
       if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

可以通过 if ((clazz.getModifiers() & Modifier.ENUM) != 0)得出结论反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败。
所以,枚举类不能通过反射破坏单例。

为什么不会被反序列化破坏?(这个是自己想的,不一定对)

因为枚举类反序列化时通过Enum类的一个valueOf方法得到的对象
这个方法的源码:

    public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    }

枚举类会将枚举对象的名字和对象,放入一个Map中,这个方法就是从map中通过枚举对象的名字得到枚举对象,从而实现反序列化。所以得到的对象还是同一个。

实现3

在这里插入图片描述
这是一个懒汉式的单例创建方法,只有第一次的时候才创建
问题就是锁的范围很大,每次想要判断对象是否为空,都要先获取锁。

怎么改进
该层双重检查,并且使用volatile改进双重检查因为指令重排而造成的问题
在这里插入图片描述
关于双重检查,可以看线程并发的笔记

实现4

在这里插入图片描述

问题1:
懒汉式。
分析:这里是有一个静态内部类来实例化对象的。按理说静态的,应该是饿汉,为什么是懒汉?因为static虽然是类加载的时候才初始化,但是这是个内部的类,只有通过getInstance才可以用到这个内部类,所以也就变成了用到的时候才创建这个对象。
问题2:
因为是static,所以JVM保证并发安全

总结

书中是推荐使用枚举来实现单例(简介,高效),他是饿汉的
此处的实现4也很不错,他是懒汉的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值