面试官问你:你知道什么是ABA问题吗?

狸猫换太子

在开始问题的阐述之前,我们先来看一则小故事:

北宋宋真宗皇后死后,当时他的两位爱妃刘妃和李妃都怀了孕,很显然,谁生了儿子,谁就有可能立为正宫。刘妃久怀嫉妒之心,唯恐李妃生了儿子被立为皇后,于是与宫中总管都堂郭槐定计,在接生婆尤氏的配合下,乘李妃分娩时由于血晕而人事不知之机,将一狸猫剥去皮毛,血淋淋,光油油地换走了刚出世的太子。刘妃命宫女寇珠勒死太子,寇珠于心不忍,暗中将太子交付宦官陈琳,陈琳将太子装在提盒中送至八贤王处抚养。再说真宗看到被剥了皮的狸猫,以为李妃产下了一个妖物,乃将其贬入冷宫。不久,刘妃临产,生了个儿子,被立为太子,刘妃也被册立为皇后。谁知六年后,刘后之子病夭。真宗再无子嗣,就将其皇兄八贤王之子(实为当年被换走的皇子)收为义子,并立为太子。

从这故事看出来,太子出生被被换成了狸猫,最后阴差阳错又回归成为太子。虽然结果是一样的,但是过程曲折了,太子真的是命途多舛啊。

为什么说这个故事?其实跟我们今天要介绍的问题有很大的关系。同样的结果,可能中间不知道发生了多少次操作,那么我们能认为他没变吗?在不同的业务场景下,我们要仔细考虑这个问题。

ABA问题描述

在多线程场景下CAS会出现ABA问题,关于ABA问题这里简单科普下,例如有2个线程同时对同一个值(初始值为A)进行CAS操作,这三个线程如下:

线程1,期望值为A,欲更新的值为B
线程2,期望值为A,欲更新的值为B

线程1抢先获得CPU时间片,而线程2因为其他原因阻塞了,线程1取值与期望的A值比较,发现相等然后将值更新为B,然后这个时候出现了线程3,期望值为B,欲更新的值为A,线程3取值与期望的值B比较,发现相等则将值更新为A,此时线程2从阻塞中恢复,并且获得了CPU时间片,这时候线程2取值与期望的值A比较,发现相等则将值更新为B,虽然线程2也完成了操作,但是线程2并不知道值已经经过了A->B->A的变化过程。

举个具体的例子说明

小明在提款机,提取了50元,因为提款机问题,有两个线程,同时把余额从100变为50:

线程1(提款机):获取当前值100,期望更新为50;
线程2(提款机):获取当前值100,期望更新为50;
线程1成功执行,线程2某种原因bl%ignore_a_1%ck了;
这时,某人给小明汇款50;
线程3(默认):获取当前值50,期望更新为100,这时候线程3成功执行,余额变为100;
线程2从Block中恢复,获取到的也是100,compare之后,继续更新余额为50。

此时可以看到,实际余额应该为100(100-50+50),但是实际上变为了50(100-50+50-50)这就是ABA问题带来错误提交结果。

解决方法

要解决ABA问题,可以增加一个版本号,当内存位置V的值每次被修改后,版本号都加1

代码示例

通过AtomicStampedReference来解决ABA问题

AtomicStampedReference内部维护了对象值和版本号,在创建AtomicStampedReference对象时,需要传入初始值和初始版本号;

当AtomicStampedReference设置对象值时,对象值以及状态戳都必须满足期望值,写入才会成功。

private static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);public static void main(String[] args) {//第一个线程 new Thread(() -> {  System.out.println("t1拿到的初始版本号:" + atomicStampedReference.getStamp());    //睡眠1秒,是为了让t2线程也拿到同样的初始版本号  try {   TimeUnit.SECONDS.sleep(1);  } catch (InterruptedException e) {   e.printStackTrace();  }  atomicStampedReference.compareAndSet(100, 101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);  atomicStampedReference.compareAndSet(101, 100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); },"t1").start();   // 第二个线程 new Thread(() -> {  int stamp = atomicStampedReference.getStamp();  System.out.println("t2拿到的初始版本号:" + stamp);    //睡眠3秒,是为了让t1线程完成ABA操作  try {   TimeUnit.SECONDS.sleep(3);  } catch (InterruptedException e) {   e.printStackTrace();  }  System.out.println("最新版本号:" + atomicStampedReference.getStamp());  System.out.println(atomicStampedReference.compareAndSet(100, 2019,stamp,atomicStampedReference.getStamp() + 1) + "\t当前值:" + atomicStampedReference.getReference()); },"t2").start();}

1、初始值100,初始版本号1
2、线程t1和t2拿到一样的初始版本号
3、线程t1完成ABA操作,版本号递增到3
4、线程t2完成CAS操作,最新版本号已经变成3,跟线程t2之前拿到的版本号1不相等,操作失败

执行结果:

t1拿到的初始版本号:1t2拿到的初始版本号:1最新版本号:3false 当前值:100

通过AtomicMarkableReference解决ABA问题

AtomicStampedReference可以给引用加上版本号,追踪引用的整个变化过程,如:A -> B -> C -> D -> A,通过AtomicStampedReference,我们可以知道,引用变量中途被更改了3次。但是,有时候,我们并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了AtomicMarkableReference,AtomicMarkableReference的唯一区别就是不再用int标识引用,而是使用boolean变量——表示引用变量是否被更改过。

private static AtomicMarkableReference atomicMarkableReference = new AtomicMarkableReference(100,false);public static void main(String[] args) {// 第一个线程 new Thread(() -> {  System.out.println("t1版本号是否被更改:" + atomicMarkableReference.isMarked());    //睡眠1秒,是为了让t2线程也拿到同样的初始版本号  try {   TimeUnit.SECONDS.sleep(1);  } catch (InterruptedException e) {   e.printStackTrace();  }  atomicMarkableReference.compareAndSet(100, 101,atomicMarkableReference.isMarked(),true);  atomicMarkableReference.compareAndSet(101, 100,atomicMarkableReference.isMarked(),true); },"t1").start();   // 第二个线程 new Thread(() -> {  boolean isMarked = atomicMarkableReference.isMarked();  System.out.println("t2版本号是否被更改:" + isMarked);    //睡眠3秒,是为了让t1线程完成ABA操作  try {   TimeUnit.SECONDS.sleep(3);  } catch (InterruptedException e) {   e.printStackTrace();  }  System.out.println("是否更改过:" + atomicMarkableReference.isMarked());  System.out.println(atomicMarkableReference.compareAndSet(100, 2019,isMarked,true) + "\t当前值:" + atomicMarkableReference.getReference()); },"t2").start();}

1、初始值100,初始版本号未被修改 false
2、线程t1和t2拿到一样的初始版本号都未被修改 false
3、线程t1完成ABA操作,版本号被修改 true
4、线程t2完成CAS操作,版本号已经变成true,跟线程t2之前拿到的版本号false不相等,操作失败

执行结果:

t1版本号是否被更改:falset2版本号是否被更改:false是否更改过:truefalse 当前值:100

多说几句

以上是本期关于CAS领域的一个经典ABA问题的解析,不知道你在实际的工作中有没有遇到过,但是在面试中这块是并发知识考查的重点。如果你还没接触过此类的问题,我的建议是你自己将上面的代码运行一下,结合理论去理解一下ABA问题所带来的问题以及如何解决他,这对你日后的开发工作也是有莫大的帮助的!

以上就是面试官问你:你知道什么是ABA问题吗?的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/893646.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月28日 16:11:11
下一篇 2025年11月28日 16:11:32

相关推荐

  • RSS订阅中的作者信息格式

    RSS和Atom中作者信息通过或标签标识,包含姓名、邮箱及网站链接,支持多作者;正确设置有助于提升内容可信度、便于追踪与SEO。 RSS订阅中的作者信息格式,主要用于标识文章的作者,让读者知道是谁写的,方便追踪特定作者的内容。格式通常包含作者姓名、邮箱,有时还会包含作者的网站链接。 作者信息的常见格…

    2025年12月17日
    000
  • XML中如何解压XML字符串_XML解压XML字符串的操作方法

    先解压再解析XML。C#用GZipStream解压字节流并转字符串,Java用GZIPInputStream或InflaterInputStream读取压缩数据,结合StreamReader或BufferedReader还原为明文XML后,交由XDocument或DocumentBuilder解析;…

    2025年12月17日
    000
  • XML中如何判断节点是否存在_XML判断节点存在性的技巧与方法

    使用XPath或find方法判断XML节点是否存在,若返回结果为空则节点不存在,结合attrib检查属性,并区分节点存在与文本内容是否为空。 在处理XML文档时,判断某个节点是否存在是一个常见需求。无论是解析配置文件、处理接口返回数据,还是进行数据校验,准确判断节点是否存在可以避免程序出错。以下是几…

    2025年12月17日
    000
  • XML中如何检查节点顺序_XML检查节点顺序的方法与技巧

    使用XPath、DOM解析、XSD约束和断言工具可检查XML节点顺序。首先通过XPath的position()函数验证节点位置,如//data/item[@type=’A’ and position()=1];其次用Python等语言解析DOM并比对实际与预期顺序;再者利用X…

    2025年12月17日
    000
  • XML与EXI压缩格式比较

    XML与EXI的核心区别在于:XML以人类可读性和互操作性为优先,适合开发调试和配置,但文件体积大、解析效率低;EXI作为W3C定义的二进制格式,牺牲可读性,通过二进制编码、字符串表、模式感知等技术实现高压缩比和高速解析,适用于带宽或资源受限场景。2. 两者并非替代关系,而是互补:XML用于数据定义…

    2025年12月17日
    000
  • RSS源如何实现内容推荐

    要实现RSS%ignore_a_1%,需在RSS数据基础上构建智能推荐系统。首先通过feedparser等工具抓取并解析RSS内容,提取标题、摘要、发布时间等信息,并存储到数据库中;对于仅提供片段的源,可结合Web Scraping技术获取全文。随后利用NLP技术对内容进行处理,包括分词、去停用词、…

    2025年12月17日
    000
  • 如何验证XML文件的语法正确性?

    验证XML语法正确性需先检查其格式良好性,再验证有效性;格式良好性确保基本语法规则如标签闭合、根元素唯一等,由解析器在解析时自动检测;有效性则通过XSD或DTD确认文档符合预定义结构,包括元素顺序、数据类型等;常用工具包括lxml(Python)、JAXP(Java)、xmllint命令行工具及ID…

    2025年12月17日
    000
  • 什么是OpenTravel标准

    OpenTravel标准是旅游行业通用的XML消息格式,由OpenTravel Alliance维护,通过定义如OTA_AirAvailRQ/RS等消息类型,实现航空公司、酒店、旅行社等系统间的数据互通;它简化集成、降低成本,并支持自动化预订与查询;尽管JSON在轻量性和解析速度上占优,但OpenT…

    2025年12月17日
    000
  • XML中如何修改节点值_XML修改节点值的实用方法与注意事项

    使用DOM、XPath或流式处理可修改XML节点值,推荐小文件用DOM+XPath、大文件用流式处理,注意编码、空节点、格式保留及备份验证。 在处理XML数据时,修改节点值是一个常见需求。无论是配置文件更新、数据转换,还是接口报文调整,掌握正确的方法至关重要。下面介绍几种实用的XML节点值修改方式,…

    2025年12月17日
    000
  • XML中如何处理空值_XML处理XML空值的技巧与方法

    使用xsi:nil=”true”显式表示XML空值,需声明命名空间并确保Schema允许;区分空字符串与缺失元素的语义差异;解析时通过DOM、SAX或XPath设置默认值;Schema设计中合理配置minOccurs和nillable属性以预防问题;关键在于各环节统一处理策略…

    2025年12月17日
    000
  • XML与JSON有何区别?如何选择?

    XML结构复杂支持属性和命名空间,适合复杂数据与行业标准;JSON语法简洁体积小,解析高效,适用于Web接口与前后端交互,现代应用多选JSON,传统系统或特定领域仍用XML。 XML和JSON都是数据交换的格式,但它们在结构、语法和使用场景上有明显不同。选择哪种取决于具体需求,比如数据复杂度、可读性…

    2025年12月17日
    000
  • 如何转换XML到数据库表

    答案:XML转数据库需分析结构、设计表、选择解析技术并处理数据类型与性能。首先解析XML层次结构,映射实体为表,属性为列,嵌套元素转子表;选用DOM或SAX等工具,结合Python、Java等语言实现ETL;注意数据类型转换、缺失值、主键设计及范式权衡;面对大文件用流式解析与批量插入优化性能,确保事…

    2025年12月17日
    000
  • 什么是GML?地理标记语言

    GML是地理信息领域的国际标准,基于XML,由OGC制定,用于统一描述、存储和交换地理空间数据。它通过定义地理特征、几何、属性、坐标系和Schema,实现跨系统互操作;支持复杂模型与语义表达,广泛应用于WFS服务和专业GIS领域,尽管存在文件冗余、解析复杂等挑战,但在高要求数据集成场景中仍具不可替代…

    2025年12月17日
    000
  • XML中如何创建XML模板_XML创建XML模板的操作步骤

    明确数据结构和用途,确定节点、层级及是否需要命名空间;2. 编写基础XML结构,用占位符标记可变内容;3. 可选添加命名空间、属性或DTD/Schema声明;4. 保存为模板文件并通过程序替换占位符复用。 在XML中创建模板,其实是指设计一个结构清晰、可复用的XML文件框架,用于后续填充数据或作为其…

    2025年12月17日
    000
  • XML中如何合并节点属性_XML合并节点属性的方法与技巧

    合并XML节点属性需基于唯一标识识别目标节点,通过编程语言(如Python)或XSLT实现属性整合。1. 使用Python的ElementTree解析XML,遍历属性并根据策略(如允许覆盖)合并;2. 利用XSLT模板匹配同名节点,复制源属性并筛选不冲突的目标属性;3. 注意处理属性冲突、确保节点唯…

    2025年12月17日
    000
  • 什么是XMDP?如何定义元数据

    XMDP是一种元数据定义的元语言,通过XML文件规范微格式中class和rel属性的语义,为HTML提供机器可读的“字典”,提升网页语义化与数据互操作性;其核心在于定义“如何定义数据”,虽在现代Web中被Schema.org等主流标准取代,但其思想对理解语义Web演进仍具价值。 XMDP,全称Ext…

    2025年12月17日
    000
  • 如何实现XML数据脱敏

    XML数据脱敏需先识别敏感信息,再结合业务需求选择替换、掩码、删除或加密等策略,利用XPath精准定位,并通过DOM、SAX或XSLT技术实现,同时兼顾结构复杂性、性能、数据一致性与合规性要求。 实现XML数据脱敏,核心在于精准识别XML文档中的敏感信息,并根据业务需求和合规性要求,运用合适的脱敏策…

    2025年12月17日
    000
  • XML中如何反序列化对象_XML反序列化XML对象的操作步骤

    答案:XML反序列化是将XML数据转换为程序对象的过程。1. 定义与XML结构匹配的类,属性名与元素名一致,使用特性或注解建立映射;2. 选用语言提供的反序列化工具,如C#的XmlSerializer或Java的JAXBContext读取XML并生成对象;3. 配置命名空间、嵌套结构等特殊格式处理;…

    2025年12月17日
    000
  • XML Schema有何作用?如何定义XSD文件?

    XML Schema用于定义XML文档结构、元素、属性及数据类型,支持命名空间和复杂约束,通过XSD文件实现数据校验与规范。 XML Schema(XML 模式)用于定义 XML 文档的结构、元素、属性及其数据类型,确保 XML 内容符合预设规则。相比 DTD,XML Schema 支持数据类型、命…

    2025年12月17日
    000
  • XML格式的电子邮件如何?SMTP协议支持吗?

    可以。邮件内容可以是XML,通过设置正确的MIME类型或将XML作为附件发送,SMTP负责传输,解析依赖客户端和接收方处理逻辑。 XML格式的电子邮件可以发送,但SMTP协议本身并不关心邮件内容是否为XML。SMTP(Simple Mail Transfer Protocol)只负责传输邮件,不解析…

    2025年12月17日
    000

发表回复

登录后才能评论
关注微信