【单例深思】单例与序列化

深思API加密狗开发避坑指南:从登录到数据读写的完整流程 本文提供了深思API加密狗开发的完整流程与关键避坑指南。详细解析了从环境初始化、设备登录到数据读写的核心步骤,重点区分了内存托管区与用户数据区的不同用途,并强调了句柄管理、错误处理及多线程安全等实战要点,帮助开发者高效稳定地完成集成。 阅读详情
在前面的文章中提到,序列化会破坏单例模式,下面用静态内部类的实现方式,说明序列化对单例的影响:

public class Singleton implements Serializable{
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton() {}
    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

判断反序列化后的对象与原来的实例是否为同一个对象?

public class Test {
    public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
        // 序列化
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("storageFile"));
        oos.writeObject(Singleton.getInstance());
        
        // 反序列化
        File file = new File("storageFile");
        ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
        Singleton newInstance = (Singleton) ois.readObject();
        
        //判断是否是同一个对象
        System.out.println(newInstance == Singleton.getInstance());
    }
}  
输出结果为:false

说明反序列化后的实例和之前序列化的实例不是同一个对象,这就可能导致JVM中出现多个实例的情况,这样就违背了单例模式的要求。怎么解决这个问题呢?

之前的文章简单提了一下,给单例实现添加readResolve()方法即可,修改后的代码如下所示:

public class Singleton implements Serializable{
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton() {}
    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    
    private Object readResolve() {
        return SingletonHolder.INSTANCE;
    }
}

相比之前只有一处改动,就是添加readResolve()方法,在方法内返回了单例,重新运行一下上述的TEST类Main方法,返回结果为:true。
结果表明,反序列化后的实例和之前被序列化的实例是同一个对象,通过这个方法确实可以解决序列化对单例模式的影响。
其他的实现方式也一样可以通过这个方法解决序列化问题。

下面我们深入了解一下readResolve()方法为什么能解决这个问题?
首先,我们需要明白在没有添加这个方法之前,为什么返回了一个新的对象实例?

简单地说,这个新对象是在反序列化过程中,通过反射调用无参数的构造方法创建的。
从源码角度看,使用ObjectInputStream进行反序列化时,会使用它的readObject()方法。

 public final Object readObject()
        throws IOException, ClassNotFoundException
    {
        if (enableOverride) {
            return readObjectOverride();
        }
        // if nested read, passHandle contains handle of enclosing object
        int outerHandle = passHandle;
        try {
            Object obj = readObject0(false); // obj 是要返回的对象
            handles.markDependency(outerHandle, passHandle);
            ClassNotFoundException ex = handles.lookupException(passHandle);
            if (ex != null) {
                throw ex;
            }
            if (depth == 0) {
                vlist.doCallbacks();
            }
            return obj;
        } finally {
            passHandle = outerHandle;
            if (closed && depth == 0) {
                clear();
            }
        }
    }  

在readObject()方法内部,通过调用readObject0()方法返回需要被返回的对象。

 private Object readObject0(boolean unshared) throws IOException {
        boolean oldMode = bin.getBlockDataMode();
        if (oldMode) {
            int remain = bin.currentBlockRemaining();
            if (remain > 0) {
                throw new OptionalDataException(remain);
            } else if (defaultDataEnd) {
                /*
                 * Fix for 4360508: stream is currently at the end of a field
                 * value block written via default serialization; since there
                 * is no terminating TC_ENDBLOCKDATA tag, simulate
                 * end-of-custom-data behavior explicitly.
                 */
                throw new OptionalDataException(true);
            }
            bin.setBlockDataMode(false);
        }
        byte tc;
        while ((tc = bin.peekByte()) == TC_RESET) {
            bin.readByte();
            handleReset();
        }
        depth++;
        totalObjectRefs++;
        try {
            switch (tc) {
                case TC_NULL:
                    return readNull();
                case TC_REFERENCE:
                    return readHandle(unshared);
                case TC_CLASS:
                    return readClass(unshared);
                case TC_CLASSDESC:
                case TC_PROXYCLASSDESC:
                    return readClassDesc(unshared);
                case TC_STRING:
                case TC_LONGSTRING:
                    return checkResolve(readString(unshared));
                case TC_ARRAY:
                    return checkResolve(readArray(unshared));
                case TC_ENUM:
                    return checkResolve(readEnum(unshared));
                case TC_OBJECT:
                    return checkResolve(readOrdinaryObject(unshared)); // 处理对象序列化
                case TC_EXCEPTION:
                    IOException ex = readFatalException();
                    throw new WriteAbortedException("writing aborted", ex);
                case TC_BLOCKDATA:
                case TC_BLOCKDATALONG:
                    if (oldMode) {
                        bin.setBlockDataMode(true);
                        bin.peek();             // force header read
                        throw new OptionalDataException(
                            bin.currentBlockRemaining());
                    } else {
                        throw new StreamCorruptedException(
                            "unexpected block data");
                    }
                case TC_ENDBLOCKDATA:
                    if (oldMode) {
                        throw new OptionalDataException(true);
                    } else {
                        throw new StreamCorruptedException(
                            "unexpected end of block data");
                    }
                default:
                    throw new StreamCorruptedException(
                        String.format("invalid type code: %02X", tc));
            }
        } finally {
            depth--;
            bin.setBlockDataMode(oldMode);
        }
    }  

对象的序列化处理在 checkResolve(readOrdinaryObject(unshared)); 方法中进行。

  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 = desc.isInstantiable() ? desc.newInstance() : null; // 反射实例化代码
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }
        passHandle = handles.assign(unshared ? unsharedMarker : obj);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(passHandle, resolveEx);
        }
        if (desc.isExternalizable()) {
            readExternalData((Externalizable) obj, desc);
        } else {
            readSerialData(obj, desc);
        }
        handles.finish(passHandle);
              // 添加了readResolve()方法后,就执行判断内的代码
        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj); // 调用readResolve方法
            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;
    }  
在  obj = desc.isInstantiable() ? desc.newInstance() : null; 这行代码中,会先判断是否实现了serializable/externalizable,如果实现了,就采用反射的方法实例化一个对象。

在这个方法内还有一个 if 判断,if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()),
其中 desc.hasReadResolveMethod() 用于判断被序列化的类中是否包含 readResolve() 方法,如果包含就返回true,在这个判断逻辑内,会执行desc.invokeReadResolve(obj);方法,该方法会反射调用我们声明的 readResolve() 方法,我们在该方法内返回了序列化的对象,因此我们也就明白了readResolve() 方法的工作原理了。

这种约定式的使用方式在 Java 中很常见的,通过这些分析我们明白了序列化机制对单例的影响,以及通过什么样的方法解决了这些影响。








国产大模型高考横评:数学推理与教育落地能力实测 大模型能力评估正从开放榜单转向封闭任务的确定性验证,高考作为强逻辑、低容错、高精度的标准化考试,成为检验模型真实推理能力的理想标尺。其核心在于考察知识覆盖、步骤完整性、单位敏感性与教学对齐度,而非单纯答案正确率。数学解答题尤其凸显模型在前提校验、条件枚举与过程可控性上的差异,反映出训练数据与教育语料深度耦合带来的工程化优势。该范式已延伸至语文语感建模、英语表达习惯适配及理综跨学科缝合等场景,为智能阅卷、精准答疑和教培系统集成提供可复现、可解释、可部署的技术依据。 阅读详情

相关推荐

17【Protues单片机仿真】基于51单片机的太阳能智能谷物翻晒机器人

基于51单片机,避障,低于50CM报警,LED灯亮起,自动翻晒用光敏电阻,光照强度大,电机转动,相当于翻晒粮食,远程控制用按键代替,按下去电机就转动,相当于翻晒粮食,然后光照强度和超声波的距离都在LCD1602显示屏显示出来。

weixin_70360261的博客 1448

序列化与单例

[size=medium]当单例模式的类实现了系列化Serializable接口,也可以通过反序列化来使它不再单例。 我们的单例类:[/size] [code="java"] public final class Singleton implements Serializable{ private static final long serialVersionUID = 173577...

乒乓狂魔的专栏 245

单例模式-序列化问题

在前面文章 单例模式-双检锁就稳了?,提到了对象序列化会破坏单例模式,同时也做了试验,今天我们来借着这个问题了解一下序列化的过程。 序列化的作用起源这里就不详细赘述了,本文主要解决序列化破坏单例的问题。如果想详细了解序列化,反序列化过程,推荐一篇文章,写得清清楚楚,包括底层实现流程。 https://cloud.tencent.com/developer/article/1125165) 问题...

qq_34679704的博客 619

Cadence Allegro16.6怎么导出器件引脚信息的报告

当我们需要用到器件的引脚相关信息时,可以利用allegro中的reports这个功能导出器件的相关信息。下面是具体的操作步骤。

suyucheng66的博客 5728

Win10+pycharm+conda 配置Siammask 过程记录(新手向)

我们跑动demo 需要配置两个相关的文件这两个文件需要用 git 进行下载,对于不熟悉git的小伙伴 我这里给出连接,大家可以直接下载即可,并将其配置在 experiments文件夹下然后大家再需要在设置参数参数这里给出了。

C1137577723的博客 550

java 单例模式 序列化_单例模式的7种实现方式及反射,序列化破坏单例模式?...

概述本文主要记录单例模式各种实现方式。主要包含以下内容单例模式含义单例特点单例缺点应用场景单例实现方式饿汉模式 (静态方法,静态块,枚举)懒汉模式(单线程,线程安全,双重验证,静态内部类)反射破环单例单例序列化单例模式单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类...

weixin_36474966的博客 247

【STM32/GD32】Keil uVision生成bin文件:从编译选项到Bootloader升级实战

本文详细讲解了如何在Keil uVision中配置STM32/GD32工程,使其在编译后自动生成Bootloader升级所需的bin文件。通过解析fromelf工具的使用、构建后命令的配置以及内存布局规划,深入阐述了bin文件在固件升级中的核心作用,并提供了串口Bootloader的实战框架与常见问题解决方案。

weixin_29190169的博客 391

单例模式与序列化那些事

本文将通过实例+阅读Java源码的方式介绍序列化是如何破坏单例模式的,以及如何避免序列化对单例的破坏。 单例模式,是设计模式中最简单的一种。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。关于单例模式的使用方式,可以阅读单例模式的七种写法 但是,单例模式真的能够实...

编程战五渣 287

Linux8.3上装Oracle 19c RAC,ssh互信总报INS-06006?别急着下补丁,试试这个临时替换scp的骚操作

本文针对在Linux 8.3上安装Oracle 19c RAC时遇到的SSH互信报错INS-06006问题,提供了一个无需官方补丁的临时解决方案。通过创建SCP包装脚本并添加-T参数,有效解决了OpenSSH 8.x版本与Oracle安装程序的兼容性问题,帮助用户顺利完成安装流程。

weixin_30764883的博客 402

Ubuntu20.04下Docker无缝升级实战指南

本文提供了一份在Ubuntu20.04系统上安全、无缝升级Docker的实战指南。文章详细阐述了升级前的版本检查、数据备份等准备工作,逐步演示了通过APT包管理器进行升级或指定版本安装的操作步骤,并强调了升级后的验证与常见问题回滚方案,确保业务容器平稳过渡。

postgres8guard的博客 478

走进序列化之基础篇(勇气篇)

  走进序列化之基础篇(勇气篇) 介绍: 基础篇:超多案例+超详细解析序列化后的二进制文件(以字节为单位分析)。 原理篇:JDK源码+Java-Object Serialization Specification官方文档解读。 终结篇:序列化机制之我见+翻译的序列化英文的官方文档PDF。 (基础篇称之为勇气篇,内容超多能看完确实需要勇气) 目的: 通过了解序列化...

JThirteen的专栏 288

Matlab实现雷达波位编排

1 简介 %本程序用于雷达球坐标系到正弦空间坐标系的转换,以及相控阵雷达的波位编排。 2 完整代码 ​%070228添加波位数据保存clear allclose allclc%波束宽度,单位:度BeamWidth = 3.6;theta05 = round(sin(BeamWidth*pi/180)*1000)/1000;%雷达作用空域AZ = [-15 15]; %方位角范围(雷达球坐标系),单位:度EL = [0 30]; %俯仰角范围(雷

qq_59747472的博客 2479

挑战10个最难回答的Java面试题(附答案)

这是有名的Java面试问题,招2~4年经验的到高级Java开发人员面试都可能碰到。这个问题的好在它能反映了面试者对等待通知机制的了解,以及他对此主题的理解是否明确。就像为什么Java中不支持多继承或者为什么String在Java中是final的问题一样,这个问题也可能有多个答案。为什么在Object类中定义wait和notify方法,每个人都能说出一些理由。从我的面试经验来看,wait和nofity仍然是大多数Java程序员最困惑的,特别是2到3年的开发人员,如果他们要求使用wait和notify,他。..

m0_72885838的博客 501

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

Java面试题

1.为什么等待和通知是在 Object 类而不是 Thread 中声明的? 一个棘手的 Java 问题,如果 Java编程语言不是你设计的,你怎么能回答这个问题呢。Java编程的常识和深入了解有助于回答这种棘手的 Java 核心方面的面试问题。 为什么 wait,notify 和 notifyAll 是在 Object 类中定义的而不是在 Thread 类中定义 这是有名的 Java 面试问题,招2~4年经验的到高级 Java 开发人员面试都可能碰到。 这个问题的好在它能反映了面试者对等待通知机制的

qq_43159578的博客 164

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

【单例深思】枚举实现单例原理

单例的枚举实现在《Effective Java》中有提到,因为其功能完整、使用简洁、无偿地提供了序列化机制、在面对复杂的序列化或者反射攻击时仍然可以绝对防止多次实例化等优点,单元素的枚举类型被作者认为是实现Singleton的最佳方法。 其实现非常简单,如下: public enum Singleton {     INSTANCE;     private Singl

InkGenius 1万+

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

枚举单例 最安全的单例,可以有效防止反射,序列化

  单例的枚举实现在《Effective Java》中有提到,因为其功能完整、使用简洁、无偿地提供了序列化机制、在面对复杂的序列化或者反射攻击时仍然可以绝对防止多次实例化等优点,单元素的枚举类型被作者认为是实现Singleton的最佳方法。 其实现非常简单,如下: public enum Singleton {     INSTANCE;     private Singleton()...

qq_39843374的博客 2232

Design pattern ——singleton pattern(单例总结深思)

Talk is cheapShowmethecode 个人学习笔记--仅供参考 单例模式可以保证内存里只有一个实例,减少了内存开销;可以避免对资源的多重占用。 单例模式看起来非常简单,实现起来其实也非常简单。 总结一下, 前几章主要讲了 饿汉式: 1,经典饿汉式 2静态代码块饿汉式 优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。 缺点:类加载的时候就...

xuedengyong的博客 155

枚举实现单例原理:线程安全及发序列化依旧为单例原因

单例的枚举实现在《Effective Java》中有提到,因为其功能完整、使用简洁、无偿地提供了序列化机制、在面对复杂的序列化或者反射攻击时仍然可以绝对防止多次实例化等优点,单元素的枚举类型被作者认为是实现Singleton的最佳方法。 其实现非常简单,如下: publi...

wangxizzz的博客 687

【单例深思】Java 单例模式全解

什么是单例模式? 单例模式 是一种对象创建模式,它用于产生一个对象的具体实例,它可以确保系统中一个类只产生一个实例,并提供一个访问它的全局访问点。 单例模式的用途? 用于创建只需要一个实例的场景。 怎么实现单例模式? 单例模式的实现中有几个共同特点: 使用 private 声明类构造函数,这样就不能通过new 声明这个

InkGenius 843

加密狗开发避坑指南:深思API的slm_user_data读写技巧与内存托管原理

本文深入解析了深思API中slm_user_data与slm_mem系列函数的核心区别与应用场景。针对加密狗开发中常见的读写失败问题,文章揭示了内存托管原理,并提供了slm_user_data读写用户数据区的实战代码与最佳实践,帮助开发者安全、高效地实现敏感数据的硬件绑定存储。

b0c1d2的博客 290

枚举实现单例原理

单例的枚举实现在《Effective Java》中有提到,因为其功能完整、使用简洁、无偿地提供了序列化机制、在面对复杂的序列化或者反射攻击时仍然可以绝对防止多次实例化等优点,单元素的枚举类型被作者认为是实现Singleton的最佳方法。其实现非常简单,如下:public enum Singleton {    INSTANCE;    private Singleton() {}}下面我们用一个枚...

幻听 825
上一篇: 【单例深思】静态内部类实现详解
下一篇: 【单例深思】枚举实现单例原理
TaiSung
博客等级 码龄15年 49粉丝 132原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值