K:枚举的线程安全性及其序列化问题

JAVA 线程安全 非线程安全 前段时间写了一个多线程,具体的功能的就是同一个请求对象去做远程调用获取数据,单线程顺序执行没有任何问题,在多线程状况下,直接执行报错,然后想到了前辈说的由于框架中使用的单例比较多,是非线程安全的 -- (当时就有一个疑问,单例模式不是线程安全的么???),使用单例的好处也是显而易见的。在此整理下那些事线程安全的,那些是非线程安全的 为初学者而著! Java基础是初学者的起点,是帮助你从小白入门到精通必学基础课程! B站最新录制Java300集>>> 适合准备入行开发的零... 阅读详情

@改进自:http://blog.youkuaiyun.com/jinzhencs/article/details/50561359

枚举是如何保证线程安全的且其在序列化和反序列化的操作中是单例的

要想看源码,首先得有一个类吧,那么枚举类型到底是什么类呢?是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()

    {

        return (T[])$VALUES.clone();

    }

 

    public static T valueOf(String s)

    {

        return (T)Enum.valueOf(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 $VALUES[];

    static

    {

        SPRING = new T("SPRING", 0);

        SUMMER = new T("SUMMER", 1);

        AUTUMN = new T("AUTUMN", 2);

        WINTER = new T("WINTER", 3);

        $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 $VALUES[];

static

{

    SPRING = new T("SPRING", 0);

    SUMMER = new T("SUMMER", 1);

    AUTUMN = new T("AUTUMN", 2);

    WINTER = new T("WINTER", 3);

    $VALUES = (new T[] {

        SPRING, SUMMER, AUTUMN, WINTER

    });

}

都是static类型的,因为static类型的属性会在类被加载之后被初始化,当一个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.

翻译如下(限于本人的英文水平,翻译不好的地方请见谅):

枚举常数的序列化方式不同于普通Serializable或者Externalizable对象的序列化(Serializable或者Externalizable对象的序列化详见序列化的相关博文:

http://blog.youkuaiyun.com/mystringisnotnull/article/details/78346886

)。枚举常数的序列化表单仅由其name属性构成;常数的字段值不存在于表单中。在序列化一个枚举常量时,ObjectOutputStream对象会写入由枚举常量的name方法所返回的值(一般也就是name属性的值,也就是枚举常量的名称)。在反序列化枚举常量时,通过ObjectInputStream对象从相关的流中读取枚举常量的name属性值(也就是枚举常量的名称),然后通过调用java.lang.Enum对象的valueOf方法,将从流中获取的枚举常量的枚举类型(指的是继承了Enum类的相关的子类)及其常量名称(也就是枚举常量的名称)一起作为该方法的参数传递给该valueOf方法。像其它Serializable或者Externalizable对象一样,枚举常量可以作为随后出现的序列化流的反向引用。枚举常量被序列化的过程无法自定义:在序列化和反序列化期间将忽略由枚举类型定义的任何特定于该类的writeObject,readObject,readObjectNoData,writeReplace和readResolve方法。类似地,任何serialPersistentFields或serialVersionUID字段声明也被将被忽略。所有的枚举类型的fixedserialVersionUID值都是0L。记录枚举类型的可序列化字段其相关的数据是不必要的,因为发送的数据类型并没有发生变化。

以下代码演示说明枚举常量的序列化只保存了其枚举常量的name属性值

package other.serial;

 

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

import java.io.Serializable;

/**

 * 该类用于测试枚举类的序列化是不是只是序列化了name属性的值,也就是枚举常量的常量名

 * @author 学徒

 *

 */

public enum AS implements Serializable

{

H("sss");

private String names;

private AS(String names)

{

this.names=names;

}

public void setNames(String names)

{

this.names=names;

}

public String getNames()

{

return this.names;

}

}

 

class AB

{

public static void main(String args[]) throws Exception

{

//设置枚举对象的names属性值为as

AS.H.setNames("as");

System.out.println("原始枚举对象的属性值为"+AS.H.getNames());

//对枚举对象进行序列化,如果其还序列化了相关的域值,则其应该保留了names属性域的值为as

new ObjectOutputStream(new FileOutputStream("H:\\as.txt")).writeObject(AS.H);

System.out.println("序列化成功");

//修改枚举对象中的names属性域的属性值

AS.H.setNames("SS");

//对已序列化的对象进行反序列化处理,若其还序列化了枚举对象的相关的域值,则其反序列化出来的names的属性值应当为as

AS a=(AS)new ObjectInputStream(new FileInputStream("H:\\as.txt")).readObject();

System.out.println("反序列化成功");

//通过输出结果判断

System.out.println("反序列化的对象的值"+a.getNames());

System.out.println(a==AS.H);

}

}

输出结果:

原始枚举对象的属性值为as

序列化成功

反序列化成功

反序列化的对象的值SS

true

通过分析其相应的输出结果可以看出枚举类型对象在序列化的时候,枚举常数的序列化仅序列化了其name属性值,也就是枚举常量名称

 

概括起来就是说,在序列化的时候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对序列化有保证。

枚举类型为什么是线程安全的?为什么反序列化枚举类型也不会创建新的实例? 枚举类型为什么是线程安全的? 我们定义的一个枚举,在第一次被真正用到的时候,会被虚拟机加载并初始化,而这个初始化过程是线程安全的。而我们知道,解决单例的并发问题,主要解决的就是初始化过程中的线程安全问题。 所以,由于枚举的以上特性,枚举实现的单例是天生线程安全的。 为什么反序列化枚举类型也不会创建新的实例? 枚举类型在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化... 阅读详情

相关推荐

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

主要介绍了java枚举是如何保证线程安全的。Java SE5提供了一种新的类型-Java的枚举类型,关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用,这是一种非常有用的功能。,需要的朋友可以参考下

为什么说枚举单例模式是最安全的?

为什么说枚举单例模式是最安全的?

weixin_41927555的博客 1140

跨平台烧录实战:RKDevTool在Windows/Linux/MacOS下的统一与差异

本文详细解析了RKDevTool在Windows、Linux和macOS三大操作系统下的固件烧录实践,重点介绍了RK3576开发板的环境配置、工具链选择、烧录模式切换及常见问题解决方案,帮助开发者构建稳定可靠的跨平台烧录工作流。

cnn8visionary的博客 206

枚举类详解

目录 1.介绍 1.1什么是枚举类? 1.1.1自定义枚举类代码演示: 1.1.2enum关键字定义枚举类 1.介绍 1.1什么是枚举类? 定义:类的对象只有有限个,确定的。比如: 星期:Monday(星期一)、......、Sunday(星期天) 性别:Man(男)、Woman(女) 季节:Spring(春节)......Winter(冬天) 订单状态:Nonpaym...

wd_888的博客 850

win10+conda+pytorch-gpu安装

pytorch 官网上只有linux和Mac的程序包,没有windows系统的,但是windows系统还是可以用pytorch的。  github: https://github.com/peterjc123/pytorch-scripts 首先要安装anaconda: 推荐清华镜像 https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ ...

一摩尔自由的博客 1万+

STM32F407 步进电机S形曲线加减速

步进电机

smallerlang的博客 1592

JAVA枚举集合序列化处理

java枚举集合序列化,枚举集合序列化, 枚举集合, 枚举序列化,@JsonSerialize

weixin_44265805的博客 1309

告别SquareLine!用免费的NXP GUI-Guider为海思3516AV300开发LVGL界面(附完整移植流程)

本文详细介绍了如何利用免费的NXP GUI-Guider工具为海思Hi3516AV300开发LVGL界面,包括环境配置、代码移植和性能优化等全流程实战指南。特别针对二维码组件缺失等常见问题提供解决方案,帮助开发者零成本构建高效嵌入式GUI系统。

weixin_28292761的博客 417

Java中枚举的线程安全性及序列化问题

要想看源码,首先得有一个类吧,那么枚举类型到底是什么类呢?是enum吗?答案很明显不是,enum就和class一样,只是一个关键字,他并不是一个类,那么枚举是由什么类维护的呢,我们简单的写一个枚举: 然后我们使用反编译,看看这段代码到底是怎么实现的,反编译后代码内容如下: 通过反编译后代码我们可以看到,,说明,该类是继承了Enum类的,同时final关键字告诉我们,这个类也是不能被继承的。当我们使用来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能

summer_fish的专栏 844

把消息说清楚:OData V2 的 sap-message、错误体 errordetails 与 ABAP 端落地实践(Gateway Foundation / RAP)

OData V2消息机制解析:SAP业务系统的结构化通信设计 本文深入剖析了SAP系统中OData V2协议的消息传递机制。该设计通过双通道(成功时使用sap-message响应头,失败时采用error响应体)实现业务数据与提示信息的结构化分离,解决了企业级应用中"只返回业务数据"或"简单报错"的不足。文章详细解读了消息结构中的关键要素:target字段实现精准UI定位、severity级别控制交互流程、transition标记区分状态消息与过渡消息。同时揭示了SAP

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 852

2025年数学建模国赛高教社杯A题、B题、C题、D题、E题详细思路和代码 模型||插值与拟合

核心思想:构造一个精确通过所有已知离散数据点(xi, yi)的函数y = f(x),并以此函数来估算或预测未知点的值。基本假设:我们认为已知的数据点是100%精确、无误差的(或者误差可忽略不计),比如理论计算值、高精度仪器测量值等。我们的目标是“还原”或“重建”出点与点之间的真实函数关系。关键特征精确性:曲线或曲面必须穿过每一个样本点。局部性:插值结果严重依赖于局部数据点的分布和数值。用途:用于“补全”数据、填充缺失值、进行函数近似等。常见方法多项式插值:拉格朗日插值、牛顿插值。

2201_76125261的博客 802

KandQ:单例模式的七种写法及其相关问题解析

设计模式中的单例模式可以有7种写法,这7种写法有各自的优点和缺点: 代码示例(java)及其分析如下: 一、懒汉式 public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getIns...

diaosangwan0363的博客 145

华为OD技术面八股文_C++_01

汇总华为OD技术面C++方向八股文真题,并提供详细解答。

qq_45776114的博客 1887

跟我学(Effective Java 2)第44条:为所有导出的API元素编写文档注释

第44条:为所有导出的API元素编写文档注释 如果要想使一个API真正可用,就必须为其编写文档。传统意义上的API文档是手动生成的,所以保持文档与代码同步是一件很繁琐的事情。Java环境提供了一种被称为Javadoc的实用工具,从而使这项任务变得很容易。Javadoc利用特格式的文档注释,根据源代码自动产生API文档。 文档注释三部分: 1.简述。文档中,对于属性和方法都是先有一个列表,然后才在后...

Dullon_jiang的博客 314

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

钻钻 “单例模式” 的牛角尖!你写的安全吗?

点击上方“Java后端技术”,选择“置顶或者星标”你关注的就是我关心的!作者:秉心说微信公众号:秉心说上一篇:奇葩面试经历分享:喊价25K,HR 却给了30K!枚举很适合...

Java后端技术 415

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

Java单例模式实现,一次性学完整,面试加分项

推荐阅读: 这套Github上40K+star学习笔记,可以帮你搞定95%以上的Java面试 毫不夸张的说,这份SpringBoot学习指南能解决你遇到的98%的问题 给跪了!这套万人期待的 SQL 成神之路PDF,终于开源了 单例模式是设计模式中使用最为普遍的一种模式。属于对象创建模式,它可以确保系统中一个类只产生一个实例。这样的行为能带来两大好处: 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销。 由于new操作的次数减少,因而对系

资料免费分享,点击名片 136

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

(*(工)*):目录

博文的标题带有大写的"K:"表示是学习到的知识点的总结,带有"Q:"的表示是相关知识点的问题及其解答,带有"KandQ:"的表示是知识点的一些细枝末节以及相关问题及其解答。博文尝试用以下的方式对文章进行分类,以便于找到知识点的归类总结及查看。目录会随着文章的更新而不断的进行跟新,以确保能够在目录处找到文章的及其相关内容。很喜欢在简书中愚行者(作者名)的一句话:我不能保证我写的每个地方都...

diaosangwan0363的博客 123

HashTable底层代码及与HashTable的差别

Hashtable简介     Hashtable同样是基于哈希表实现的,同样每个元素是一个key-value对,其内部也是通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。     Hashtable也是JDK1.0引入的类,是线程安全的,能用于多线程环境中。     Hashtable同样实现了Serializable接口,它支持序列化,实现了Cloneabl

weixin_40667145的博客 3137

第五篇:JAVA集合之Hashtable源码剖析

Hashtable简介     Hashtable同样是基于哈希表实现的,同样每个元素是一个key-value对,其内部也是通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。     Hashtable也是JDK1.0引入的类,是线程安全的,能用于多线程环境中。     Hashtable同样实现了Serializable接口,它支持序列化,实现了Cloneabl

u012426327的专栏 354

C++ JSON库nlohmann/json:现代C++数据序列化与配置管理实战

JSON作为一种轻量级的数据交换格式,在Web API、配置文件和数据持久化等场景中广泛应用。其核心原理是基于文本的键值对与数组嵌套结构,易于人类阅读和机器解析。在C++生态中,高效、安全地处理JSON数据是提升开发效率的关键技术。nlohmann/json库通过深度运用现代C++特性(如模板元编程、操作符重载),将JSON对象映射为原生C++类型,实现了近乎脚本语言般的直观操作体验。该库支持单头文件零依赖集成、类型安全访问、自定义类型序列化以及JSON Patch等高级功能,显著降低了数据解析与序列化的复

weixin_30723433的博客 359

Java的基本问题

JavaGuide Java基本功 1.Java语言特点:面向对象,跨平台,支持多线程,支持网络编程,健壮性,安全性,编译与解释并存(具有编译型语言和解释型语言的特征) 2.JVM:Java虚拟机,用于运行Java字节码(.class文件),不同的系统使用的是相同的字节码,将.class文件通过JVM解释为机器可以执行的二级制机器码。 3.JDK&&JRE:JDK拥有JRE的一切,还有编译器和工具,能够创建和编译程序。JRE是Java运行时环境。 4.Open JDK和Oracle JDK:

J_i_ang_的博客 270

java 设计模式_创建型_1单例模式 5种方式

单例模式是一种确保类只有一个实例的创建型设计模式,主要特点是通过私有构造方法和静态方法控制对象创建。常见实现方式包括饿汉式(类加载时创建对象,线程安全但耗内存)、懒汉式(首次调用时创建,需同步保证线程安全)、双重检测锁(优化同步效率但不推荐使用)、静态内部类(兼顾线程安全和延迟加载)以及枚举(天然单例,避免反射和反序列化漏洞)。单例模式优化了资源共享,但需注意反射和反序列化可能破坏单例,可通过异常控制和readResolve()方法防范。根据资源占用和加载需求,枚举适合简单场景,静态内部类适合资源较大的延迟

qq_39275653的博客 566
上一篇: K:java序列化和反序列化
下一篇: K:Serializable或Externalizable对象的序列化
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值