设计模式之单列模式

文章首发:设计模式之单列模式

单例模式简介

单例模式可以说是最简单的模式之一,属于创建型模式。单一的类负责创建自己的对象,同时确保该类有且仅有一个对象被创建。同时这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

  • 单例类智能有一个实例
  • 单例类必须自己创建自己的唯一实例,外部不能去创建
  • 单例类必须提供一个方法来获取该类的对象

单列模式的实现主要有:饿汉式和懒汉式两种。下面将详细讲解这两种实现方法。代码的实现中主要使用lombok工具包。

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <!--springboot工程可省略version-->
    <version>1.16.18</version>    
</dependency>

饿汉式

@Data //lombok注解
@AllArgsConstructor(access = AccessLevel.PRIVATE) //lombok注解 私有化全参构造方法
public class SingletonHunger {

    //关键 可以使用静态变量,也可以使用静态代码块
    private static final SingletonHunger INSTANCE = new SingletonHunger();
    private String name;
    private String age;
    //私有化空参构造,无法在其他类直接new对象
    private SingletonHunger() {
    }

    public static SingletonHunger getInstance1(String name, String age) {
        INSTANCE.setAge(age);
        INSTANCE.setName(name);
        return INSTANCE;
    }
}

饿汉式就是在类加载的时候就去就去创建对象,主要是static关键字,static修饰的静态变量或方法在类加载的时候就会被加载到方法区中,只会初始化一次。从static关键字的特性就能很好地理解饿汉式了。对于这个饿汉式可以延伸去了解类的加载,例如java.lang.ClassLoader这个类。

饿汉式是会在类加载的时候就回去实例化对象,虽然可以保证的对象的单例,但是会在一开始就加载很多的对象,一下子内存就上涨了。也有可能加载了不需要的类对象,所以就有了懒汉式。

懒汉式

    懒汉式在需要的时候才去实例化对象。即在调用单例类提供方法的时候,采取实例化对象。懒汉式的实现也有很多种。

懒汉式:同步方法--->同步代码块(不怎么推荐)

@Data
public class SingletonLazy {
    private volatile static SingletonHunger instance;
    private String name;
    private String age;
    //私有化空参构造,无法在其他类直接new对象
    private SingletonHunger() {
    }
    //最简单、最low的写法,有线程安全的问题
    public static SingletonHunger getInstanceV1(String name, String age) {
        instance = new SingletonHunger();
        instance.setAge(age);
        instance.setName(name);
        return instance;
    }
    //改进V1->V2,解决线程安全最简单、最懒和最烂的做法
    public synchronized static SingletonHunger getInstanceV2(String name, String age) {
        instance = new SingletonHunger();
        instance.setAge(age);
        instance.setName(name);
        return instance;
    }
    //V3 这个也就那样...
    public static SingletonHunger getInstanceV3(String name, String age) {
        if(instance==null){
            synchronized (SingletonLazy.class) {
            instance = new SingletonHunger();
            instance.setAge(age);
            instance.setName(name);    
        }
        return instance;
    }
    
    //上面三种的缺点就不多说了,也不没必要多看。当然还有著名DCL(Double Check Lock),这个可以多讲
    public static SingletonLazy getInstanceByDCL(String name, String age) {
        //1.对象还没初始化instance为空,当大量的线程"同时到达"一个if时,假设是长时间的大量并发的情况下
        //12.假如在第一个线程成功初始化对象的之后(instance不为空),如果还有线程进来,就直接返回instance
        if (instance == null) {
            //2.到达这里只有一个线程拿到锁---> 8.之前没拿到锁的线程,在第一个线程释放锁后,其余的线程来哄抢
            //大量的线程拿不到锁被阻塞(后面的线程就和第二个线程一样的执行流程一样了)
            synchronized (SingletonLazy.class) {
                //3.再次判空 ---> 9.第二个线程拿到锁的线程再次判空,由于第一个线程已经初始化,instance不为空
                if (instance == null) {
                    //4.第一个拿到锁的线程初始化对象
                    instance = new SingletonLazy();
                    instance.setAge(age);
                    instance.setName(name);
                }
            }
            //5.第一个线程执行完,释放锁 ---> 10.第二个线程释放锁
        }
        //6.第一个线程得到对象 11.第二线程拿到同样的对象
        return instance;
    }
}

当然synchronized可以换成Lock,也可以换成自旋锁。自旋锁也是很能装逼,在jdk1.8中的ConcurrentHashMap中就换成了自旋锁。DCL的方法虽然还算可以,但是还有更好的方法。DCL其中还有一个关键就是volatile关键字,就是在instance = new SingletonLazy()时防止指令重排。volatile和自旋锁可以实现轻量级锁。

静态内部类Holder

    下面介绍静态内部内类的懒汉式实现:
@Data
public class SingletonLazy implements Serializable {

    private String name;
    private String age;

    private SingletonLazy() {
    }

    /**
     * 类加载时类加载器操作会加锁(JVM底层实现){@link ClassLoader}
     *
     * @param name
     * @param age
     * @return
     */
    public static SingletonLazy getInstanceByHolder(String name, String age) {
        //调用静态内部内类
        return SingletonLazy.InstanceHolder.getInstance(name, age);
    }
    //静态内部类
    private static class InstanceHolder {
        private static final SingletonLazy LAZY = new SingletonLazy();

        private static SingletonLazy getInstance(String name, String age) {
            LAZY.setAge(age);
            LAZY.setName(name);
            return LAZY;
        }
    }
}

静态内部内类只有在调用的时候才会初始化。第一次调用SingletonLazy.InstanceHolder.getInstance(name, age)的时候静态内部类初始化,同时创建外部内的对象。static修饰只会初始化一次。其实静态内部类的原理时利用了ClassLoader的机制,ClassLoader的加载Class的方法有synchronized关键字修饰。在类加载到完成就存在加锁和释放锁的操作。比DCL的方式就少了synchronized或者Lock代码块的加锁和释放锁的操作。所以静态内部类更加常用,比DCL更好,毕竟能不加锁最好就别加锁。至于"自旋锁"则是CAS机制,详细可以去了解JUC的相关知识。PS:后面也会出关于JUC的相关文章。

注册式单列

    注册式单例就是把实例化对象放在一个Map集合中类的全限定类名作为key,该类的是实例化对象作为value
public class SingletonRegistryContainer {

    private static final Map<String, Object> IOC = new ConcurrentHashMap<>();
    private static Lock lock = new ReentrantLock();
    private SingletonRegistryContainer() {
    }

    /**
     * 借鉴spring中的单列注册
     * @param className 全限定类名
     * @return
     */
    public static Object getBean(String className) {
        Object obj;
        if (!IOC.containsKey(className)) {
            lock.lock();
            try {
                if (!IOC.containsKey(className)) {
                    //通过反射创建对象
                    obj = Class.forName(className).newInstance();
                    IOC.put(className, obj);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        return IOC.get(className);
    }
}

简单的看一下Spring中单列对象的注册:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    protected static final Object NULL_OBJECT = new Object();
    protected final Log logger = LogFactory.getLog(this.getClass());
    //存放单例对象
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap(64);
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
    private final Map<String, Object> earlySingletonObjects = new HashMap(16);
    private final Set<String> registeredSingletons = new LinkedHashSet(64);
    private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap(16));
    private final Set<String> inCreationCheckExclusions = Collections.newSetFromMap(new ConcurrentHashMap(16));
    private Set<Exception> suppressedExceptions;
    private boolean singletonsCurrentlyInDestruction = false;
    private final Map<String, Object> disposableBeans = new LinkedHashMap();
    private final Map<String, Set<String>> containedBeanMap = new ConcurrentHashMap(16);
    private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap(64);
    private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap(64);

    public DefaultSingletonBeanRegistry() {
    }
    //注册单例对象
    public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
        Assert.notNull(beanName, "'beanName' must not be null");
        synchronized(this.singletonObjects) {
            Object oldObject = this.singletonObjects.get(beanName);
            if (oldObject != null) {
                throw new IllegalStateException("Could not register object [" + singletonObject + "] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound");
            } else {
                //不存在,添加
                this.addSingleton(beanName, singletonObject);
            }
        }
    }
    //添加单例对象到map中
    protected void addSingleton(String beanName, Object singletonObject) {
        synchronized(this.singletonObjects) {
            this.singletonObjects.put(beanName, singletonObject != null ? singletonObject : NULL_OBJECT);
            this.singletonFactories.remove(beanName);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
    //篇幅长,其他省略
    ...       
}

枚举式单例

枚举式单例这就厉害,枚举类天生就是单列的。相信看过<Effetvice Java>这本书中的"用私有构造器或者枚举类型强化Singleton"就说到使用枚举单列来强化单例(编写一个仅含有单个元素的枚举类型),因为就算是私有化构造方法,但是还是可以通过"反射""反序列化"就可以破解单例(这个下一个小节介绍,以及其解决方法),枚举类型却能够防止"反射""反序列化"的破坏。下面看一下代码实现:

package com.msr.study.patterns.creational.singleton;

import java.io.Serializable;
public enum SingletonRegistryEnum implements Serializable {

    INSTANCE;
    
    //反射和反序列化破坏时会用到
    private Object data;
    
    public static SingletonRegistryEnum getInstance() {
        return INSTANCE;
    }

    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
}

//测试
package com.msr.study.patterns.creational.singleton;
public class SingletonTest {
    public static void main(String[] args) throws Exception {
        SingletonRegistryEnum instance1 = SingletonRegistryEnum.getInstance();
        SingletonRegistryEnum instance2 = SingletonRegistryEnum.getInstance();
        System.out.println(instance1==instance2); //true
    }
}

枚举类的源码也就这么简单,很难看得出来为什么枚举是单例的。但是我们可以通过反编译其class文件去一探究竟,下面使用到jad反编译工具。下载好jad之后,通过命令行执行。具体其他的用法可以百度

jad E:\java\...\SingletonRegistryEnum.class(class文件绝对路径)

执行后就会在就会在jad所在的目录生成SingletonRegistryEnum.jad,通过文本编辑器打开它

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   SingletonRegistryEnum.java

package com.msr.study.patterns.creational.singleton;
import java.io.Serializable;
public final class SingletonRegistryEnum extends Enum implements Serializable{

    public static SingletonRegistryEnum[] values(){
        return (SingletonRegistryEnum[])$VALUES.clone();
    }

    public static SingletonRegistryEnum valueOf(String name){
        return (SingletonRegistryEnum)Enum.valueOf(com/msr/study/patterns/creational/singleton/SingletonRegistryEnum, name);
    }
    //没有空参的构造方法
    //私有化的构造方法,两个参数---下一小节的反射破坏枚举时就会凸显出来了
    private SingletonRegistryEnum(String s, int i) {
        super(s, i);
    }

    public static SingletonRegistryEnum getInstance() {
        return INSTANCE;
    }

    public Object getData(){
        return data;
    }

    public void setData(Object data){
        this.data = data;
    }

    public static final SingletonRegistryEnum INSTANCE;
    private Object data;
    private static final SingletonRegistryEnum $VALUES[];

    static {
        //静态代码块,近初始化一次,有且仅有一份---天生单例
        INSTANCE = new SingletonRegistryEnum("INSTANCE", 0);
        $VALUES = (new SingletonRegistryEnum[] {
            INSTANCE
        });
    }
}

重头戏:单列对象的暴力破解(反射和反序列化)

上面的内容讲了那么多,又是否真的是真正可以做到单例呢?答案是不一定哟~

总所周知jdk的反射是十分强大,毕竟有人说反射是框架设计的灵魂,可见其功能的强大。它可以在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。使用过反射的都知道,就算是private修饰,一样是可以通过反射去获取,setAccessible()private上天。

把对象转换为字节序列的过程称为对象的序列化。把字节序列恢复为对象的过程称为对象的反序列化。一般用途:把对象的字节序列化通过文件流永久地保存在磁盘中;在网络在传输对象的字节序列,也就是为什么在用json工具或者在web项目运行的时候,可能会出现Serializable的一些异常,有可能就是实体类没有继承Serializable接口。

下面编写一个基于序列化和反序列化去克隆对象的工具类。并且是深度克隆,在原型模式也可以使用这个工具类,不用去重写clone()就可以达到深度克隆。而这个工具类的序列化和反序列化是基于内存,垃圾回收时就会清理,基于内存速度快。

package com.msr.study.patterns.creational.singleton;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.io.*;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ObjectCloneUtil {

    public static <T extends Serializable> T clone(T obj) throws Exception {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        //垃圾回收时就会清理
        ObjectOutputStream oos = new ObjectOutputStream(bout);
        oos.writeObject(obj);
        ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bin);
        return (T) ois.readObject();
        // 说明:调用 ByteArrayInputStream 或 ByteArrayOutputStream 对象的 close 方法没有任何意义
        // 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放
        // 网上很多的反序列化的的例子用的是文件流:把对象序列化到磁盘的文件上,在读取文件反序列化生成对象
    }
}

现在开始尝试用放射和反序列化破坏上面所说的单例模式的实现

public class BreakTest {
    public static void main(String[] args) {
        try {
            System.out.println("==========破坏饿汉式==========");
            //通过提供的接口去获取对象
            SingletonHunger instance = SingletonHunger.getInstance();
            //反射破坏
            Class<?> aClass = Class.forName(SingletonHunger.class.getName());
            Constructor<?> constructor = aClass.getDeclaredConstructor();
            constructor.setAccessible(true);
            //反射获取
            SingletonHunger reflectObj = (SingletonHunger) constructor.newInstance();
            System.out.println(reflectObj == instance); //false
            //反序列化破坏
            SingletonHunger clone = ObjectCloneUtil.clone(instance);
            System.out.println(clone == instance); //false

            System.out.println("==========破坏DCL==========");
            //通过提供的接口去获取对象
            SingletonLazy instance2 = SingletonLazy.getInstanceByDCL("张三", "16");
            //反射破坏
            Class<?> aClass2 = Class.forName(SingletonLazy.class.getName());
            Constructor<?> constructor2 = aClass2.getDeclaredConstructor();
            constructor2.setAccessible(true);
            SingletonLazy reflectObj2 = (SingletonLazy) constructor2.newInstance();
            System.out.println(reflectObj2 == instance2);
            //反序列化破坏
            SingletonLazy clone2 = ObjectCloneUtil.clone(instance2);
            System.out.println(clone2 == instance2);

            System.out.println("==========破坏静态内部类==========");
            SingletonHolder instance3 = SingletonHolder.getInstanceByHolder();
            
            Class<?> aClass3 = Class.forName(SingletonHolder.class.getName());
            Constructor<?> constructor3 = aClass3.getDeclaredConstructor();
            constructor3.setAccessible(true);
            SingletonHolder reflectObj3 = (SingletonHolder) constructor3.newInstance();
            System.out.println(instance3 == reflectObj3); //false

            SingletonHolder clone3 = ObjectCloneUtil.clone(instance3);
            System.out.println(clone3 == instance3); //false
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在通过反射去获取枚举类型的构造函数的时候,会出现NoSuchMethodException异常,在上一小节中通过对枚举类型的反编译之后,会发现只有一个两个参数的私有的构造方法。没有无参的构造方法。去获取两个参数的构造方法的时候,虽然成功获取不过还是会报错Cannot reflectively create enum objects,很明显是"不能通过反射去创建枚举对象"。

System.out.println("==========破坏枚举类型单例==========");
SingletonRegistryEnum instance4 = SingletonRegistryEnum.getInstance();
Class<?> aClass4 = Class.forName(SingletonRegistryEnum.class.getName());
//会直接报错:
//java.lang.NoSuchMethodException:com.msr.study.patterns.creational.singleton.SingletonRegistryEnum.<init>()
Constructor<?> constructor4 = aClass4.getDeclaredConstructor();
constructor4.setAccessible(true);
//那就去获取两个参数的构造方法 
Constructor<?> constructor5 = aClass4.getDeclaredConstructor(String.class,int.class);
constructor5.setAccessible(true);
//newInstance()报错:java.lang.IllegalArgumentException: Cannot reflectively create enum objects
SingletonRegistryEnum reflectObj4 =(SingletonRegistryEnum) constructor4.newInstance()
//反编译的片段
private SingletonRegistryEnum(String s, int i) {
        super(s, i);
}

WTF???为什么呢?下面来通过去看一下newInstance()的源代码:

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);
            }
        }
        //这些判断条件是本地方法,是通过底层的JVM去判断的
        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;
    }

所以功能强大的反射遇到枚举类型就不太好使了。那么反序列化呢,它又会不会破坏单列?下面测试一下:

SingletonRegistryEnum instance4 = SingletonRegistryEnum.getInstance();
SingletonRegistryEnum registryEnum = ObjectCloneUtil.clone(instance4);
System.out.println(instance4==registryEnum); //结果是:true

为什么这么神奇?其实可以看一下反序列化中的ObjectInputStream的readObject()方法的源码:

public final Object readObject()throws IOException, ClassNotFoundException{
        if (enableOverride) {
            return readObjectOverride();
        }
        ...
        try {
            //关键方法
            Object obj = readObject0(false);
            ...
            return obj;
        } finally {
            ...
        }
}

private Object readObject0(boolean unshared) throws IOException {
                ...
                //switch语句
                case TC_ENUM:
                    //关键readEnum()方法
                    return checkResolve(readEnum(unshared));

                ...
}

private Enum<?> readEnum(boolean unshared) throws IOException {
        if (bin.readByte() != TC_ENUM) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        if (!desc.isEnum()) {
            throw new InvalidClassException("non-enum class: " + desc);
        }

        int enumHandle = handles.assign(unshared ? unsharedMarker : null);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(enumHandle, resolveEx);
        }

        String name = readString(false);
        Enum<?> result = null;
        Class<?> cl = desc.forClass();
        if (cl != null) {
            try {
                @SuppressWarnings("unchecked")
                Enum<?> en = Enum.valueOf((Class)cl, name);
                result = en;
            } catch (IllegalArgumentException ex) {
                throw (IOException) new InvalidObjectException(
                    "enum constant " + name + " does not exist in " +
                    cl).initCause(ex);
            }
            if (!unshared) {
                handles.setObject(enumHandle, result);
            }
        }

        handles.finish(enumHandle);
        passHandle = enumHandle;
        return result;
    }

可以发现枚举类型其实通过类名和 Class 对象类找到一个唯一的枚举对象。因此枚举对象不可能被类加载器加载多次。

解决方法:对于反序列化的解决方法就是在类中添加readResolve()方法。例如在静态内部类添加了之后,再去尝试使用反序列化破坏单例就不好使了。

private Object readResolve(){
        return SingletonHolder.getInstanceByHolder();
}

原因是ObjectInputStream中的readObject()方法--->readObject0()方法--->readOrdinaryObject()方法

private Object readOrdinaryObject(boolean unshared)throws IOException{
        if (bin.readByte() != TC_OBJECT) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        desc.checkDeserialize();

        Class<?> cl = desc.forClass();
        if (cl == String.class || cl == Class.class
                || cl == ObjectStreamClass.class) {
            throw new InvalidClassException("invalid class descriptor");
        }

        Object obj;
        try {
            //调用了 ObjectStreamClass 的 isInstantiable()方法
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

        ...

        return obj;
    }

//代码很简单
boolean isInstantiable() {
    requireInitialized();
    return (cons != null);
}

isInstantiable()方法的代码很简单,就是判断一下构造方法是否为空,构造方法不为空就返回true。那就是意味着只要有无参构造方法就会实例化,很明显编写的单例类是含有一个无参的构造方法,最后`obj = desc.isInstantiable() ? desc.newInstance() : null;三目运算符还是创建了一个新对象,我们还需要再往下看readOrdinaryObject()方法中的代码。

private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        if (bin.readByte() != TC_OBJECT) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        desc.checkDeserialize();

        Class<?> cl = desc.forClass();
        if (cl == String.class || cl == Class.class
                || cl == ObjectStreamClass.class) {
            throw new InvalidClassException("invalid class descriptor");
        }

        Object obj;
        try {
            //obj被创建,不为空
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

        ...
        //if判断,调用hasReadResolveMethod()
        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                // Filter the replacement object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep);
            }
        }

        return obj;
    }

再进一步又调用了hasReadResolveMethod()方法。逻辑又是非常简单,就是判断一下readResolveMethod是否为空。那么readResolveMethod 是在哪里赋值的呢?通过全局查找找到了赋值代码在私有方法ObjectStreamClass()方法中给 readResolveMethod 进行赋值。

boolean hasReadResolveMethod() {
    requireInitialized();
    return (readResolveMethod != null);
}

readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);

其实就是通过反射区找到一个无参的readResolve()方法,并且保存下来。所以再回到ObjectInputStream 的 readOrdinaryObject()方法继续往下看,如果存在readResolve()方法就会调用 invokeReadResolve()方法,然后返回readResolveMethod.invoke(obj, (Object[]) null);的结果。

Object invokeReadResolve(Object obj) throws IOException, UnsupportedOperationException{
        requireInitialized();
        if (readResolveMethod != null) {
            try {
                //调用readResolveMethod
                return readResolveMethod.invoke(obj, (Object[]) null);
            } catch (InvocationTargetException ex) {
                Throwable th = ex.getTargetException();
                if (th instanceof ObjectStreamException) {
                    throw (ObjectStreamException) th;
                } else {
                    throwMiscException(th);
                    throw new InternalError(th);  // never reached
                }
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError(ex);
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }

其实在这程中还是会有一个新的对象产生,只不过是没有被返回,返回的是前面创建的对象,这样就会产生垃圾对象了。那如果,创建对象的动作发生频率增大,就意味着内存分配开销也就随之增大。

其实还有一种单例:线程内单例(ThreadLocal),那只是针对单个线程内,这里就不展开讲了。

总结

枚举类能够同时防止反射和反序列化的破坏,其他的方法只能通过添加Resolve()方法,至于反射就不太好解决。其实可以在开发的时候明确单例类不能通过反射去创建对象实例,并且在私有的构造方法内做一点保护措施。

if (lazy==null){
    throw new RuntimeException("不能反射创建对象");
}else {
    throw new RuntimeException("对象已创建");
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容