几种单例模式的对比

单例模式有好几种实现方式:饿汉式、懒汉式、DCL、静态内部类、枚举等。

本文主要探讨几种实现方式是否实现了真正意义上的单例——只创建一个对象。

创建对象有这么几种方式new、克隆、序列化、反射

几种单例模式实现时都会将构造函数声明为private,所以new这种方式此时认为不可用,那么着重看其他几种是否能保证真正的单例。

序列化

@Slf4j
public class Singleton implements Serializable{
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }

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

        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("SingletonTest"));
        objectOutputStream.writeObject(singleton);
        objectOutputStream.close();

        File file = new File("SingletonTest");
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
        Singleton newSingleton = (Singleton)objectInputStream.readObject();
        file.delete();
        log.info("反序列化后是否和原对象相同:{}", singleton == newSingleton);//false
    }
}

可以看到反序列化会生成一个新的对象。

原因就在反序列化时调用的readObject方法。debug进去看,调用链为readObject->readObject0->readOrdinaryObject

obj = desc.isInstantiable() ? desc.newInstance() : null;


boolean isInstantiable() {
    requireInitialized();
    return (cons != null);//当类实现了Serializable接口时,cons会被初始化为一个无参构造函数
}

Object newInstance()
        throws InstantiationException, InvocationTargetException,
               UnsupportedOperationException
    {
        requireInitialized();
        if (cons != null) {
            try {
                //构造函数调用newInstance()生成新对象
                return cons.newInstance();
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError(ex);
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }

最终还是通过无参构造函数调用newInstance()生成新对象,所以反序列化会生成一个新的对象。序列化破坏了单例

生成对象的下面还有一段代码

if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())//是否覆写了readResolve方法
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                handles.setObject(passHandle, obj = rep);
            }
        }

判断是否实现了readResolve方法,如果实现了,则调用readResolve方法,生成rep对象,将刚才通过反射得到的对象替换掉。所以我们可以这么写

@Slf4j
public class Singleton implements Cloneable, Serializable{
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }

    //覆写readResolve方法
    public Object readResolve() {
        return INSTANCE;
    }

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

        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("SingletonTest"));
        objectOutputStream.writeObject(singleton);
        objectOutputStream.close();

        File file = new File("SingletonTest");
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
        Singleton newSingleton = (Singleton)objectInputStream.readObject();
        file.delete();
        log.info("反序列化后是否和原对象相同:{}", singleton == newSingleton);//true
    }
}

readResolve返回单例,所以最终输出结果为true。

但是通过readResolve来解决序列化的问题就安全了吗,没有,参见《Effective Java》,具体原因后面再说。

克隆

@Slf4j
public class Singleton implements Cloneable {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }

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

        log.info("克隆后是否和原对象相同:{}", singleton == singleton.clone());//false
    }
}

克隆也会生成一个新的对象,克隆破坏了单例。但是我们可以让覆写的clone方法返回INSTANCE。

@Slf4j
public class Singleton implements Cloneable {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return INSTANCE;
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }

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

        log.info("克隆后是否和原对象相同:{}", singleton == singleton.clone());//true
    }
}

此时,克隆出的对象和之前的一样。所以要么不实现Cloneable接口,要么覆写clone方法,返回已生成的单例。

反射

前面说这么多,在反射面前都跟纸一样,构造函数不是声明为private的了吗,简单,反射下,就拿到了

@Slf4j
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }

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

        Class clazz = Singleton.class;
        Constructor declaredConstructor = clazz.getDeclaredConstructor();//获得无参构造函数
        declaredConstructor.setAccessible(true);//破除private限制
        Singleton singleton1 = (Singleton) declaredConstructor.newInstance();

        log.info("反射后是否和原对象相同:{}", singleton == singleton1);//false
    }
}

就算在构造函数加标记位防止二次调用,依然可以用反射破解。反射破坏了单例

前面都是以饿汉式举例,懒汉式、静态内部类、DCL都一样。唯独枚举特殊,下面单独列出。

枚举序列化

public enum EnumSingleton {
    INSTANCE
}

@Slf4j
public class Singleton {
    public static void main(String[] args) throws Exception {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("SingletonTest"));
        objectOutputStream.writeObject(EnumSingleton.INSTANCE);
        objectOutputStream.close();

        File file = new File("SingletonTest");
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
        EnumSingleton newSingleton = (EnumSingleton)objectInputStream.readObject();
        file.delete();

        log.info("枚举反序列化后是否和原对象相同:{}", newSingleton == EnumSingleton.INSTANCE);//true
    }
}

枚举反序列化后没有生成新的对象。看下为什么?关键代码如下

//readObject0方法中的关键代码
return checkResolve(readEnum(unshared));


//readEnum方法中的关键代码
Enum<?> en = Enum.valueOf((Class)cl, name);
result = en;

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<String, T> enumConstantDirectory() {
        if (enumConstantDirectory == null) {
            T[] universe = getEnumConstantsShared();
            if (universe == null)
                throw new IllegalArgumentException(
                    getName() + " is not an enum type");
            Map<String, T> m = new HashMap<>(2 * universe.length);
            for (T constant : universe)
                m.put(((Enum<?>)constant).name(), constant);
            enumConstantDirectory = m;
        }
        return enumConstantDirectory;
    }

T[] getEnumConstantsShared() {
        if (enumConstants == null) {
            if (!isEnum()) return null;
            try {
                final Method values = getMethod("values");
                java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedAction<Void>() {
                        public Void run() {
                                values.setAccessible(true);
                                return null;
                            }
                        });
                @SuppressWarnings("unchecked")
                T[] temporaryConstants = (T[])values.invoke(null);
                enumConstants = temporaryConstants;
            }
            // These can happen when users concoct enum-like classes
            // that don't comply with the enum spec.
            catch (InvocationTargetException | NoSuchMethodException |
                   IllegalAccessException ex) { return null; }
        }
        return enumConstants;
    }

最终调用的Enum的valueOf方法,枚举的相关特性参见(枚举),name传入的就是单例的名称(这里是INSTANCE)。getEnumConstantsShared方法通过反射拿到values方法,调用values方法得到存放枚举的数组。enumConstantDirectory拿到数据后,将其中的name做为key,实例作为value存入map中,valueOf通过name从map中取到对应的实例。在这个过程中,并没有生成新的实例,反序列化没有破坏枚举的单例

枚举克隆

public enum EnumSingleton implements Cloneable {
    INSTANCE;

    @Override//会报错,无法覆写clone方法
    public Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }
}


//Enum.java
protected final Object clone() throws CloneNotSupportedException {
    throw new CloneNotSupportedException();
}

当你尝试覆写clone方法的时候,会报错,因为enum其实是继承了Enum类的,而Enum中将clone方法声明为final方法了,无法覆写。枚举天然不支持clone,更不会被克隆破坏单例

枚举反射

@Slf4j
public class Singleton {
    public static void main(String[] args) throws Exception {
        Class clazz = EnumSingleton.class;
        Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        EnumSingleton newSingleton = (EnumSingleton) declaredConstructor.newInstance("INSTANCE", 0);

        log.info("枚举反射后是否和原对象相同:{}", newSingleton == EnumSingleton.INSTANCE);
    }
}

//报错
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)

尝试对枚举进行反射的时候会报错,看下为什么。

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;
    }

newInstance方法中会判断是否为枚举类型,如果是枚举,直接抛出异常,也就是上面打出的异常信息。反射无法破坏枚举的单例

总结

  • 饿汉式、懒汉式、静态内部类、DCL等实现单例的方式会被序列化/反序列化破坏,一个解决方法是重写readReslove方法返回已生成的单例。克隆也会破坏单例,所以要么不实现Cloneable,要么重写clone方法返回已生成的单例。反射可以破坏单例,生成新实例。
  • 枚举天然不支持克隆,反射。序列化/反序列化时利用Enum的valueOf保证单例不被破坏。《Efficient Java》也建议枚举是实现单例的最佳方式。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值