深度分析Java的枚举类型—-枚举的线程安全性及序列化问题

2023-05-16

写在前面:Java SE5提供了一种新的类型-Java的枚举类型,关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用,这是一种非常有用的功能。本文将深入分析枚举的源码,看一看枚举是怎么实现的,他是如何保证线程安全的,以及为什么用枚举实现的单例是最好的方式。

枚举是如何保证线程安全的

要想看源码,首先得有一个类吧,那么枚举类型到底是什么类呢?是enum吗?答案很明显不是,enum就和class一样,只是一个关键字,他并不是一个类,那么枚举是由什么类维护的呢,我们简单的写一个枚举:

public enum t {
    SPRING,SUMMER,AUTUMN,WINTER;
}

然后我们使用反编译,看看这段代码到底是怎么实现的,反编译(Java的反编译)后代码内容如下:

public final class T extends Enum
{
    private T(String s, int i)
    {
        super(s, i);
    }
    public static T[] values()
    {
        T at[];
        int i;
        T at1[];
        System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i);
        return at1;
    }

    public static T valueOf(String s)
    {
        return (T)Enum.valueOf(demo/T, s);
    }

    public static final T SPRING;
    public static final T SUMMER;
    public static final T AUTUMN;
    public static final T WINTER;
    private static final T ENUM$VALUES[];
    static
    {
        SPRING = new T("SPRING", 0);
        SUMMER = new T("SUMMER", 1);
        AUTUMN = new T("AUTUMN", 2);
        WINTER = new T("WINTER", 3);
        ENUM$VALUES = (new T[] {
            SPRING, SUMMER, AUTUMN, WINTER
        });
    }
}

通过反编译后代码我们可以看到,public final class T extends Enum,说明,该类是继承了Enum类的,同时final关键字告诉我们,这个类也是不能被继承的。当我们使用enmu来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承,我们看到这个类中有几个属性和方法。

我们可以看到:

        public static final T SPRING;
        public static final T SUMMER;
        public static final T AUTUMN;
        public static final T WINTER;
        private static final T ENUM$VALUES[];
        static
        {
            SPRING = new T("SPRING", 0);
            SUMMER = new T("SUMMER", 1);
            AUTUMN = new T("AUTUMN", 2);
            WINTER = new T("WINTER", 3);
            ENUM$VALUES = (new T[] {
                SPRING, SUMMER, AUTUMN, WINTER
            });
        }

都是static类型的,因为static类型的属性会在类被加载之后被初始化,我们在深度分析Java的ClassLoader机制(源码级别)和Java类的加载、链接和初始化两个文章中分别介绍过,当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。所以,创建一个enum类型是线程安全的

为什么用枚举实现的单例是最好的方式

在[转+注]单例模式的七种写法中,我们看到一共有七种实现单例的方式,其中,Effective Java作者Josh Bloch 提倡使用枚举的方式,既然大神说这种方式好,那我们就要知道它为什么好?

1. 枚举写法简单

写法简单这个大家看看[转+注]单例模式的七种写法里面的实现就知道区别了。

public enum EasySingleton{
    INSTANCE;
}

你可以通过EasySingleton.INSTANCE来访问。

2. 枚举自己处理序列化

我们知道,以前的所有的单例模式都有一个比较大的问题,就是一旦实现了Serializable接口之后,就不再是单例得了,因为,每次调用 readObject()方法返回的都是一个新创建出来的对象,有一种解决办法就是使用readResolve()方法来避免此事发生。但是,为了保证枚举类型像Java规范中所说的那样,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定。原文如下:

Enum constants are serialized differently than ordinary serializable or externalizable objects. The serialized form of an enum constant consists solely of its name; field values of the constant are not present in the form. To serialize an enum constant, ObjectOutputStream writes the value returned by the enum constant’s name method. To deserialize an enum constant, ObjectInputStream reads the constant name from the stream; the deserialized constant is then obtained by calling the java.lang.Enum.valueOf method, passing the constant’s enum type along with the received constant name as arguments. Like other serializable or externalizable objects, enum constants can function as the targets of back references appearing subsequently in the serialization stream. The process by which enum constants are serialized cannot be customized: any class-specific writeObject, readObject, readObjectNoData, writeReplace, and readResolve methods defined by enum types are ignored during serialization and deserialization. Similarly, any serialPersistentFields or serialVersionUID field declarations are also ignored–all enum types have a fixedserialVersionUID of 0L. Documenting serializable fields and data for enum types is unnecessary, since there is no variation in the type of data sent.

大概意思就是说,在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。 我们看一下这个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 const " + enumType +"." + name);  
        }  

从代码中可以看到,代码会尝试从调用enumType这个Class对象的enumConstantDirectory()方法返回的map中获取名字为name的枚举对象,如果不存在就会抛出异常。再进一步跟到enumConstantDirectory()方法,就会发现到最后会以反射的方式调用enumType这个类型的values()静态方法,也就是上面我们看到的编译器为我们创建的那个方法,然后用返回结果填充enumType这个Class对象中的enumConstantDirectory属性。

所以,JVM对序列化有保证。

3.枚举实例创建是thread-safe(线程安全的)

我们在深度分析Java的ClassLoader机制(源码级别)和Java类的加载、链接和初始化两个文章中分别介绍过,当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。所以,创建一个enum类型是线程安全的

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深度分析Java的枚举类型—-枚举的线程安全性及序列化问题 的相关文章

  • 使用 REST API 实现属性/字段级安全

    我正在为支持多租户授权模型的 REST API 构建概念验证 该模型不仅控制用户可以访问哪些对象 还控制对象中的字段 此模型的目标是确保租户管理员只能修改其租户并且只能查看允许的对象属性 我有一个正在开发的现有代码库 可在以下位置公开获取
  • Java泛型类型要么扩展要么是父类

    我正在寻找一些如下所示的代码 public class Parent
  • Java 区域设置区分大小写

    我有以下代码来显示当前区域设置 System out println Locale getDefault System out println new Locale en US 上面给出的输出如下 en US en us 如何构造一个 Lo
  • 可以使用注解进行代码注入吗?

    我意识到这可能是一个已经被提出和回答的问题 但请耐心等待 我想知道是否可以使用注释将代码注入到类编译时 典型的示例是为对象的成员生成 getter 和 setter 这并不完全是我所需要的 但它可以说明基本思想 现在在互联网上我得到的基本答
  • 正则表达式查找两个字符之间的内部匹配

    环境 Java 我想匹配两个字符串之间的字符 这是一个例子 foo
  • 原型 Bean 未按预期自动装配

    测试控制器 java RestController public class TestController Autowired private TestClass testClass RequestMapping value test me
  • CreationException:无法在 Play 2.5.18 中创建注入器错误,以使用 com.google.inject.AbstractModule 替换 GlobalSettings Java 代码

    我正在将 Play 应用程序从 2 5 12 升级到 2 5 18 当我启动该应用程序时 使用sbt 我收到此错误 CreationException 无法创建注入器 看到以下错误 1 Error injecting constructor
  • 如何用Java捕获音频数据

    我想访问我的麦克风用 Java 录制的音频数据 我该怎么做呢 我的目标是保存录制的音频数据并同时向用户播放 如果您不需要 JMF 中的任何附加功能 我会避免使用它 因为开发已经停止 最后一个版本是 2004 年 它与 Java 6 存在兼容
  • c3p0 Java 数据库池、故障转移配置

    当数据库关闭时 IP 和端口会自动切换到另一个数据库服务器 我应该如何配置 Web 应用程序的 c3p0 连接池以遵循此数据库故障转移机制 目前 我使用的是 c3p0 但是在上次数据库故障转移中 池连接无法重新建立 请求失败后重新建立 有助
  • 使用 JNDI 添加 LDAP 条目

    我正在尝试使用 JNDI 将条目添加到 LDAP 服务器 我可以成功地从 LDAP 服务器读取条目 但是当我尝试添加新条目时出现错误 我检查了各种方法但都失败了 private String getUserAttribs String se
  • 使用 GSON 将 JSON 字符串转换为 Java 对象

    我正在尝试将 json 解析为 java 根据 jsonlint com 我有以下字符串 该字符串是有效的 json private final static String LOC JSON lat1 39 737567 lat2 32 7
  • 调用本机方法时返回 java.lang.UnsatisfiedLinkError

    我正在尝试为第三方 DLL 制作 Java 包装器 我创建了自己的 DLL 充当 JNI 和第三方 DLL 之间的中间人 在java中我加载这个DLL很好但是错误java lang UnsatisfiedLinkError sixense
  • Spring Hibernate中的@Transient方法调用

    我有一个 Pojo 类 在其中创建一个未与数据库表映射的字段 所以我必须声明字段Declaration和setter和getter方法 Transient 否则会显示错误 Transient private String docHistor
  • 使用 testcontainer 作为 Dockerfile 的一部分运行测试

    我的 dockerfile 看起来像这样 FROM maven 3 jdk 11 slim COPY pom xml COPY src src RUN mvn clean install 这意味着构建的一部分是单元测试的执行 一些单元测试使
  • Java 堆分析因 SIGABRT 崩溃

    我正在尝试分析由 C 编写的方法分配并插入的本机内存JVM通过JNI 我安装了 valgrind version valgrind 3 13 0 并尝试使用以下选项运行 JVM valgrind tool massif massif out
  • Apache Beam:如何在使用重复数据删除功能时解决“ParDo 需要确定性密钥编码器才能使用状态和计时器”

    我正在尝试使用 Apache Beam 的重复数据删除功能对来自 Google Cloud Pubsub 的输入消息进行重复数据删除 但是 我创建后遇到错误KV
  • Jersey:返回字符串列表

    我尝试以 JSON 和 XML 形式返回 Jersey 中的字符串列表 我以为这会是微不足道的 我的第一次尝试是写这样的东西 GET Produces MediaType APPLICATION JSON MediaType APPLICA
  • Spring MVC 和复选框

    我正在使用 Spring MVC 3 0 并且不能完全看到这个问题的所有部分 我的控制器将生成一个域对象列表 假设有一个简单的 User 对象 具有firstName lastName age 和role 属性 我想在表中输出该用户列表 每
  • java银行程序帐户ID不上去?

    每次创建银行帐户时 帐户 ID 都应增加 1 但每次我尝试提取 Id 时 我只会得到帐户 ID 为 0 任何建议 因为我完全按照我学习的书中的方式进行操作而且它仍然没有更新 帐户构造函数 public class BankAccount p
  • 无法以联觉方式绘制像素、Pi 数

    我想将 pi 数字的每个数字打印为彩色像素 因此 我得到一个带有 pi 数字的输入 然后将其解析为一个列表 每个节点包含一个数字 我知道 稍后我将使用一个数组 但我从来没有把它画到屏幕上 有人能帮我看看我错在哪里吗 import java

随机推荐

  • jetsonNX刷机步骤

    图为T503盒子 1 取消ssd为系统盘 nbsp sudo mount nbsp dev mmcblk0p1 mnt nbsp cd mnt etc nbsp sudo rm setssdroot conf nbsp reboot 2 格
  • MT7603/MT7610/MT7612/MT7632/MT7662/RT3070 WiFi模块选型参考

    MT7603 MT7610 MT7612 MT7632 MT7662 RT3070 rt5572系列wifi模块选型参考 RT3070是2 4G单通道 xff0c 最大传输速率可以150Mbps xff0c 目前基本上是一些dongle类产
  • 联发科RT2880/RT3052/RT3883/RT5350/RT3352无线路由器wifi芯片介绍

    RT3052 SOC结合了mediaTek Ralink 的802 11n草案 xff0c 兼容2T2R MAC BBP RF 高性能的384MHz MIPS24KEc CPU内核 5端口集成10 100以太网交换机 PHY USB OTG
  • MT7628处理器介绍,MT7628芯片原理图资料

    MT7628处理器 xff1a MT7628芯片上路由器包括802 11n MAC和基带 2 4GHz无线电和FEM 575 580 MHz MIPS 24KCPU核 5端口10 100快速以太网交换机 MT7628包括所有需要的东西 从单
  • MT7628 wifi模块,MTK路由器芯片介绍

    MT7628处理器 xff1a MT7628nn mt7628an 系列产品是新一代2T2R 802 11n Wi Fi AP 路由器 系统单芯片 MT7628可提升射频效能表现 减低功耗 xff0c 并将整体物料清单 BOM 成本优化 x
  • MT7621A路由器芯片参数/处理器资料(原理图/CPB)介绍

    MT7621A支持高级别AP 路由器的要求 xff0c 以及大量的接口以及巨大的最大RAM容量 feature MT7621A CPU MIPS1004Kc 880 MHz I Cache D Cache 32 KB 32 KB L2 Ca
  • 《大数据时代》读书总结

    这本书从以下几个方面阐述了我们所处的时代是如何展现 大数据 的 xff1a 1 首先是大数据时代的思维变革 思维为什么需要变革 xff1f 怎样变革 xff1f 思维需要变革的第一个原因是 xff0c 这个时代获取和处理数据的方式更加多元
  • 联发科RT3573无线wifi路由器模块芯片介绍

    RT3573是联发科推出的一款高效能又具成本效益的802 11n WiFi 传输器 dongle 解决方案平台 RT3573是一款高度整合式WiFi单芯片 xff0c 支持450 Mbps PHY速率 它完全符合IEEE 802 11n及I
  • K8S svc暴露的服务内部调用方法

    现象 xff1a 在Kubernetes集群内业务是通过ClusterIP或者服务名访问 k8s 的ingress nginx controller存在缺陷 xff0c 只有Ingress的Pod所在节点上 xff0c Pod才能访问通过I
  • CMake构建OpenCV项目

    文章目录 前言一 基本概念二 操作步骤1 创建OpenCV程序2 创建CMake文件3 编译项目4 运行项目 总结 前言 CMake是个一个开源的跨平台自动化建构系统 xff0c 用来管理软件建置的程序 xff0c 并不依赖于某特定编译器
  • 在立创商城上快速制作PCB原理图库

    以可调降压电源芯片TPS62130为例 xff1a 1 打开立创商城 xff0c 搜索元器件 2 点击 下载文件 3 出现下图 xff0c 点击 立即使用 4 保存原理图文件 5 保存完 xff0c 导出 6 打开导出文件 7 生成原理图库
  • 串口通信(232,485,422)及一些常见问题

    本文转载自21ic电子网 xff1a https www sohu com a 197785266 464086 本人对此文章进行了优化 xff0c 如有侵权 xff0c 请联系删除 xff01 并行通信与串行通信 与串行通信相对的是并行通
  • 运维排查篇 | 大量后台进程占用CPU资源怎么办?

    目录 案例现象定位问题解决问题pstree 案例现象 今天早上打开虚拟机终端 xff0c 发现一直有进程往我的 1 txt 文件里写东西 xff0c 删除之后又重新生成 而且 1 txt 大小已经五百多M了 使用 top 命令查看一下 xf
  • VTOL-垂起4+2构型电机电调校准(小白自用

    我也是个小白 有不对的地方欢迎指出 xff0c 有啥可以在评论区讨论 目录 VTOL 垂起4 43 2构型电机电调校准 xff08 小白自用参考链接 xff0c 感谢前辈博主前期准备校准四旋翼四轴电机电调双发电调校准 一些可能会出现的问题以
  • 理解浮点数的二进制表示

    目录 二进制的科学计数法 浮点数的二进制表示 符号位 尾数和指数 xff08 以64位浮点数为例 xff09 0 规约数和非规约数 无穷大和空值NaN 二进制的科学计数法 浮点数在电脑中用二进制储存 xff0c 约定以二进制的科学计数法来进
  • 最新版 如何获取OSS配置获取AccessKeyId、AccessKeySecret

    1 首先第一步 登录阿里云官网 https www aliyun com spm 61 5176 12901015 2 0 0 3c89525ce8lmgE 2 注册账号 可以直接使用 支付宝扫码登录 自动注册完成 3 注册成功后登录 可以
  • 消息邮箱和消息队列

    邮箱是一个通过在系统共享存储区内传递消息来实现同步和通信的对象 每个邮箱包含一个用于发送消息的消息队列和一个用来接受消息的消息队列 由于是在共享存储区域 xff0c 因此它对每个任务都是可见的 而一般的消息队列 xff0c 还可用来处理任务
  • git clone到本地之后切换分支

    Lenovo 64 DESKTOP GOJ0H91 MINGW64 d GitHub u boot khadas vims nougat git checkout 检查属于哪个分支 Your branch is up to date wit
  • win10开启自带移动热点以及解决无法设置移动热点,请打开WLAN问题

    1 选择Windows设置里的 网络和Internet 2 选择移动热点 xff0c 点击开 如果这一步显示 无法设置移动热点 xff0c 请打开WLAN 解决方法 xff1a 右键单击计算机 管理 设备管理器 xff0c 然后点击菜单栏的
  • 深度分析Java的枚举类型—-枚举的线程安全性及序列化问题

    写在前面 xff1a Java SE5提供了一种新的类型 Java的枚举类型 xff0c 关键字enum可以将一组具名的值的有限集合创建为一种新的类型 xff0c 而这些具名的值可以作为常规的程序组件使用 xff0c 这是一种非常有用的功能